@stackql/provider-utils 0.4.1 → 0.4.3
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 +2 -1
- package/package.json +1 -1
- package/src/docgen/resource/methods.js +28 -4
- package/src/providerdev/analyze.js +165 -12
- package/src/providerdev/generate.js +77 -4
- package/src/providerdev/split.js +30 -44
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# StackQL Provider Utils
|
|
2
2
|
|
|
3
|
-

|
|
3
|
+

|
|
4
|
+

|
|
4
5
|
|
|
5
6
|
A comprehensive toolkit for transforming OpenAPI specifications into StackQL providers. This library streamlines the process of parsing, mapping, validating, testing, and generating documentation for StackQL providers.
|
|
6
7
|
|
package/package.json
CHANGED
|
@@ -4,6 +4,24 @@ import {
|
|
|
4
4
|
sanitizeHtml
|
|
5
5
|
} from '../helpers.js';
|
|
6
6
|
|
|
7
|
+
const getRequiredBodyParams = (methodDetails, accessType) => {
|
|
8
|
+
// Only process request body for insert, update, replace, and exec
|
|
9
|
+
if (!['insert', 'update', 'replace', 'exec'].includes(accessType)) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Get required body params if they exist
|
|
14
|
+
const requiredBodyProps = methodDetails.requestBody?.required ? methodDetails.requestBody.required : [];
|
|
15
|
+
|
|
16
|
+
// For insert, update, and replace, prefix with data__
|
|
17
|
+
if (['insert', 'update', 'replace'].includes(accessType)) {
|
|
18
|
+
return requiredBodyProps.map(prop => `data__${prop}`);
|
|
19
|
+
} else {
|
|
20
|
+
// For exec, don't prefix
|
|
21
|
+
return requiredBodyProps;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
7
25
|
export function createMethodsSection(resourceData, dereferencedAPI) {
|
|
8
26
|
|
|
9
27
|
let content = `\n## Methods\n\n`;
|
|
@@ -37,10 +55,16 @@ export function createMethodsSection(resourceData, dereferencedAPI) {
|
|
|
37
55
|
for (const [methodName, methodDetails] of Object.entries(methods)) {
|
|
38
56
|
console.info(`Adding ${accessType} method to table: ${methodName}`);
|
|
39
57
|
|
|
58
|
+
// Get required params from both the standard params and the request body
|
|
59
|
+
const reqParamsArr = Object.keys(methodDetails.requiredParams || {});
|
|
60
|
+
const reqBodyParamsArr = getRequiredBodyParams(methodDetails, accessType);
|
|
61
|
+
|
|
62
|
+
// Combine both types of required parameters
|
|
63
|
+
const allReqParamsArr = [...reqParamsArr, ...reqBodyParamsArr];
|
|
64
|
+
|
|
40
65
|
// Format required params as comma-delimited list with hyperlinks
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
? requiredParamsArr.map(param => `<a href="#parameter-${param}"><code>${param}</code></a>`).join(', ')
|
|
66
|
+
const requiredParamsStr = allReqParamsArr.length > 0
|
|
67
|
+
? allReqParamsArr.map(param => `<a href="#parameter-${param}"><code>${param}</code></a>`).join(', ')
|
|
44
68
|
: '';
|
|
45
69
|
|
|
46
70
|
// Format optional params as comma-delimited list with hyperlinks
|
|
@@ -48,7 +72,7 @@ export function createMethodsSection(resourceData, dereferencedAPI) {
|
|
|
48
72
|
const optionalParamsStr = optionalParamsArr.length > 0
|
|
49
73
|
? optionalParamsArr.map(param => `<a href="#parameter-${param}"><code>${param}</code></a>`).join(', ')
|
|
50
74
|
: '';
|
|
51
|
-
|
|
75
|
+
|
|
52
76
|
// Add the method row to the table
|
|
53
77
|
content += `
|
|
54
78
|
<tr>
|
|
@@ -51,6 +51,108 @@ function extractMain2xxResponse(responseObj) {
|
|
|
51
51
|
return '';
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Detect if an object should have an objectKey and determine what it should be
|
|
56
|
+
* @param {Object} spec - Full OpenAPI spec
|
|
57
|
+
* @param {Object} operation - Operation object
|
|
58
|
+
* @returns {string} - Suggested objectKey or empty string if none
|
|
59
|
+
*/
|
|
60
|
+
function detectObjectKey(spec, operation) {
|
|
61
|
+
// Only applicable for GET operations
|
|
62
|
+
if (!operation || !operation.responses) {
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let responseObject = null;
|
|
67
|
+
let responseCode = null;
|
|
68
|
+
|
|
69
|
+
// Find the first 2xx response
|
|
70
|
+
for (const [code, response] of Object.entries(operation.responses)) {
|
|
71
|
+
if (code.startsWith('2')) {
|
|
72
|
+
responseCode = code;
|
|
73
|
+
// Handle direct response or reference
|
|
74
|
+
if (response.$ref) {
|
|
75
|
+
// Resolve reference
|
|
76
|
+
const refParts = response.$ref.split('/');
|
|
77
|
+
const componentType = refParts[2];
|
|
78
|
+
const responseName = refParts[3];
|
|
79
|
+
responseObject = spec.components?.[componentType]?.[responseName];
|
|
80
|
+
} else {
|
|
81
|
+
responseObject = response;
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!responseObject) {
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Get the schema from the response
|
|
92
|
+
let schema = null;
|
|
93
|
+
|
|
94
|
+
// If it's a direct response object
|
|
95
|
+
if (responseObject.content?.['application/json']?.schema) {
|
|
96
|
+
schema = responseObject.content['application/json'].schema;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// If the schema is a reference, resolve it
|
|
100
|
+
if (schema && schema.$ref) {
|
|
101
|
+
const refParts = schema.$ref.split('/');
|
|
102
|
+
const componentType = refParts[2];
|
|
103
|
+
const schemaName = refParts[3];
|
|
104
|
+
schema = spec.components?.[componentType]?.[schemaName];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Handle allOf case (like in the droplets_list example)
|
|
108
|
+
if (schema && schema.allOf) {
|
|
109
|
+
// Look at the first object in allOf that has properties
|
|
110
|
+
for (const subSchema of schema.allOf) {
|
|
111
|
+
if (subSchema.properties && Object.keys(subSchema.properties).length === 1) {
|
|
112
|
+
const key = Object.keys(subSchema.properties)[0];
|
|
113
|
+
return `$.${key}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Handle direct properties case (like in the droplets_get example)
|
|
119
|
+
if (schema && schema.properties) {
|
|
120
|
+
// If there's only one property at the top level, and it's not a primitive
|
|
121
|
+
const propKeys = Object.keys(schema.properties);
|
|
122
|
+
if (propKeys.length === 1) {
|
|
123
|
+
const key = propKeys[0];
|
|
124
|
+
const prop = schema.properties[key];
|
|
125
|
+
|
|
126
|
+
// Check if the property is an object or array, not a primitive
|
|
127
|
+
if (prop.$ref ||
|
|
128
|
+
prop.type === 'object' ||
|
|
129
|
+
prop.type === 'array' ||
|
|
130
|
+
(prop.properties && Object.keys(prop.properties).length > 0)) {
|
|
131
|
+
return `$.${key}`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return '';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Map HTTP verb to SQL verb
|
|
141
|
+
* @param {string} httpVerb - HTTP verb (get, post, put, etc)
|
|
142
|
+
* @returns {string} - Corresponding SQL verb
|
|
143
|
+
*/
|
|
144
|
+
function mapToSqlVerb(httpVerb) {
|
|
145
|
+
const verbMap = {
|
|
146
|
+
'get': 'select',
|
|
147
|
+
'post': 'insert',
|
|
148
|
+
'delete': 'delete',
|
|
149
|
+
'put': 'replace',
|
|
150
|
+
'patch': 'update'
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return verbMap[httpVerb] || 'exec';
|
|
154
|
+
}
|
|
155
|
+
|
|
54
156
|
/**
|
|
55
157
|
* Find existing mapping in x-stackQL-resources
|
|
56
158
|
* @param {Object} spec - OpenAPI spec
|
|
@@ -77,10 +179,14 @@ function findExistingMapping(spec, pathRef) {
|
|
|
77
179
|
}
|
|
78
180
|
}
|
|
79
181
|
|
|
182
|
+
// Get objectKey if present
|
|
183
|
+
const objectKey = method.response?.objectKey || '';
|
|
184
|
+
|
|
80
185
|
return {
|
|
81
186
|
resourceName,
|
|
82
187
|
methodName,
|
|
83
|
-
sqlVerb
|
|
188
|
+
sqlVerb,
|
|
189
|
+
objectKey
|
|
84
190
|
};
|
|
85
191
|
}
|
|
86
192
|
}
|
|
@@ -91,10 +197,28 @@ function findExistingMapping(spec, pathRef) {
|
|
|
91
197
|
return {
|
|
92
198
|
resourceName: '',
|
|
93
199
|
methodName: '',
|
|
94
|
-
sqlVerb: ''
|
|
200
|
+
sqlVerb: '',
|
|
201
|
+
objectKey: ''
|
|
95
202
|
};
|
|
96
203
|
}
|
|
97
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Escape and sanitize a CSV field value
|
|
207
|
+
* @param {string} value - Field value to escape
|
|
208
|
+
* @returns {string} - Escaped value
|
|
209
|
+
*/
|
|
210
|
+
function escapeCsvField(value) {
|
|
211
|
+
if (!value) return '';
|
|
212
|
+
|
|
213
|
+
// If the value contains commas, double quotes, or newlines, wrap it in quotes
|
|
214
|
+
// and escape any existing double quotes by doubling them
|
|
215
|
+
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
|
216
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return value;
|
|
220
|
+
}
|
|
221
|
+
|
|
98
222
|
/**
|
|
99
223
|
* Analyze OpenAPI specs and generate mapping CSV
|
|
100
224
|
* @param {Object} options - Options for analysis
|
|
@@ -133,7 +257,8 @@ export async function analyze(options) {
|
|
|
133
257
|
existingMappings[key] = {
|
|
134
258
|
resourceName: row.stackql_resource_name || '',
|
|
135
259
|
methodName: row.stackql_method_name || '',
|
|
136
|
-
sqlVerb: row.stackql_verb || ''
|
|
260
|
+
sqlVerb: row.stackql_verb || '',
|
|
261
|
+
objectKey: row.stackql_object_key || ''
|
|
137
262
|
};
|
|
138
263
|
}
|
|
139
264
|
})
|
|
@@ -159,7 +284,7 @@ export async function analyze(options) {
|
|
|
159
284
|
|
|
160
285
|
// Only write header if creating a new file
|
|
161
286
|
if (!fileExists) {
|
|
162
|
-
writer.write('filename,path,operationId,formatted_op_id,verb,response_object,tags,formatted_tags,stackql_resource_name,stackql_method_name,stackql_verb\n');
|
|
287
|
+
writer.write('filename,path,operationId,formatted_op_id,verb,response_object,tags,formatted_tags,stackql_resource_name,stackql_method_name,stackql_verb,stackql_object_key,op_description\n');
|
|
163
288
|
}
|
|
164
289
|
|
|
165
290
|
const files = fs.readdirSync(inputDir);
|
|
@@ -214,17 +339,45 @@ export async function analyze(options) {
|
|
|
214
339
|
const pathRef = `#/paths/${encodedPath}/${verb}`;
|
|
215
340
|
|
|
216
341
|
// Find existing mapping if available
|
|
217
|
-
|
|
342
|
+
let { resourceName, methodName, sqlVerb, objectKey } = findExistingMapping(spec, pathRef);
|
|
343
|
+
|
|
344
|
+
// CHANGE 1: Default methodName to formattedOpId if not found
|
|
345
|
+
if (!methodName) {
|
|
346
|
+
methodName = formattedOpId;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// CHANGE 2: Default sqlVerb based on HTTP verb if not found
|
|
350
|
+
if (!sqlVerb) {
|
|
351
|
+
sqlVerb = mapToSqlVerb(verb);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// CHANGE 3: Detect and set objectKey for GET operations if not already set
|
|
355
|
+
if (!objectKey && verb === 'get') {
|
|
356
|
+
objectKey = detectObjectKey(spec, operation);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Get operation description
|
|
360
|
+
const opDescription = operation.summary || operation.description || '';
|
|
218
361
|
|
|
219
|
-
// Escape commas
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
362
|
+
// Escape fields that might contain commas, quotes, or other special characters
|
|
363
|
+
const escapedFields = {
|
|
364
|
+
filename: escapeCsvField(filename),
|
|
365
|
+
path: escapeCsvField(pathKey),
|
|
366
|
+
operationId: escapeCsvField(operationId),
|
|
367
|
+
formattedOpId: escapeCsvField(formattedOpId),
|
|
368
|
+
verb: escapeCsvField(verb),
|
|
369
|
+
responseRef: escapeCsvField(responseRef),
|
|
370
|
+
tagsStr: escapeCsvField(tagsStr),
|
|
371
|
+
formattedTags: escapeCsvField(formattedTags),
|
|
372
|
+
resourceName: escapeCsvField(resourceName),
|
|
373
|
+
methodName: escapeCsvField(methodName),
|
|
374
|
+
sqlVerb: escapeCsvField(sqlVerb),
|
|
375
|
+
objectKey: escapeCsvField(objectKey),
|
|
376
|
+
opDescription: escapeCsvField(opDescription)
|
|
377
|
+
};
|
|
225
378
|
|
|
226
379
|
// Write row
|
|
227
|
-
writer.write(`${filename},${
|
|
380
|
+
writer.write(`${escapedFields.filename},${escapedFields.path},${escapedFields.operationId},${escapedFields.formattedOpId},${escapedFields.verb},${escapedFields.responseRef},${escapedFields.tagsStr},${escapedFields.formattedTags},${escapedFields.resourceName},${escapedFields.methodName},${escapedFields.sqlVerb},${escapedFields.objectKey},${escapedFields.opDescription}\n`);
|
|
228
381
|
}
|
|
229
382
|
}
|
|
230
383
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// src/providerdev/generate.js
|
|
2
1
|
import fs from 'fs';
|
|
3
2
|
import path from 'path';
|
|
4
3
|
import yaml from 'js-yaml';
|
|
@@ -75,14 +74,15 @@ function getSuccessResponseInfo(operation) {
|
|
|
75
74
|
.sort();
|
|
76
75
|
|
|
77
76
|
if (twoXxCodes.length === 0) {
|
|
78
|
-
|
|
77
|
+
throw new Error('No 2xx response found, openAPIDocKey is required');
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
const lowest2xx = twoXxCodes[0];
|
|
82
81
|
const content = responses[lowest2xx]?.content || {};
|
|
83
82
|
const mediaTypes = Object.keys(content);
|
|
84
83
|
|
|
85
|
-
|
|
84
|
+
// Default to 'application/json' if mediaType is not found
|
|
85
|
+
const mediaType = mediaTypes.length > 0 ? mediaTypes[0] : 'application/json';
|
|
86
86
|
|
|
87
87
|
return {
|
|
88
88
|
mediaType,
|
|
@@ -99,6 +99,71 @@ function snakeCase(name) {
|
|
|
99
99
|
return name.replace(/-/g, '_');
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Count the number of path parameters in a path
|
|
104
|
+
* @param {string} path - HTTP path
|
|
105
|
+
* @returns {number} - Number of path parameters
|
|
106
|
+
*/
|
|
107
|
+
function countPathParams(path) {
|
|
108
|
+
// Match all path parameters like {param_name}
|
|
109
|
+
const matches = path.match(/\{[^}]+\}/g);
|
|
110
|
+
return matches ? matches.length : 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Sort operations from most specific to least specific based on path parameters
|
|
115
|
+
* @param {Object} resources - Resources object containing methods and sqlVerbs
|
|
116
|
+
* @param {Object} spec - Full OpenAPI specification
|
|
117
|
+
* @returns {Object} - Resources with sorted sqlVerbs
|
|
118
|
+
*/
|
|
119
|
+
function sortOperationsBySpecificity(resources, spec) {
|
|
120
|
+
// For each resource
|
|
121
|
+
for (const resourceName in resources) {
|
|
122
|
+
const resource = resources[resourceName];
|
|
123
|
+
const methods = resource.methods;
|
|
124
|
+
|
|
125
|
+
// Create a map of method references to their specificity (path param count)
|
|
126
|
+
const methodSpecificityMap = {};
|
|
127
|
+
|
|
128
|
+
// For each method, find its operation ref and count path params
|
|
129
|
+
for (const methodName in methods) {
|
|
130
|
+
const method = methods[methodName];
|
|
131
|
+
const operationRef = method.operation.$ref;
|
|
132
|
+
|
|
133
|
+
// Extract path and verb from the reference
|
|
134
|
+
// Reference format: '#/paths/{encodedPath}/{verb}'
|
|
135
|
+
const refParts = operationRef.split('/');
|
|
136
|
+
const verb = refParts.pop();
|
|
137
|
+
// Remove '#/paths/' and the verb, then decode the path
|
|
138
|
+
const encodedPath = refParts.slice(2).join('/');
|
|
139
|
+
const path = encodedPath.replace(/~1/g, '/');
|
|
140
|
+
|
|
141
|
+
// Count path parameters
|
|
142
|
+
const paramCount = countPathParams(path);
|
|
143
|
+
|
|
144
|
+
// Store the method reference and its path parameter count
|
|
145
|
+
const methodRef = `#/components/x-stackQL-resources/${resourceName}/methods/${methodName}`;
|
|
146
|
+
methodSpecificityMap[methodRef] = paramCount;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// For each SQL verb, sort the operations by specificity
|
|
150
|
+
for (const verbName in resource.sqlVerbs) {
|
|
151
|
+
const operations = resource.sqlVerbs[verbName];
|
|
152
|
+
|
|
153
|
+
if (operations && operations.length > 0) {
|
|
154
|
+
// Sort operations from most specific (more path params) to least specific
|
|
155
|
+
operations.sort((a, b) => {
|
|
156
|
+
const aRef = a.$ref;
|
|
157
|
+
const bRef = b.$ref;
|
|
158
|
+
return methodSpecificityMap[bRef] - methodSpecificityMap[aRef];
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return resources;
|
|
165
|
+
}
|
|
166
|
+
|
|
102
167
|
/**
|
|
103
168
|
* Generate StackQL provider extensions
|
|
104
169
|
* @param {Object} options - Options for generation
|
|
@@ -234,6 +299,11 @@ export async function generate(options) {
|
|
|
234
299
|
response: responseInfo
|
|
235
300
|
};
|
|
236
301
|
|
|
302
|
+
// Add objectKey to the response info if it exists in the manifest and is for a GET operation
|
|
303
|
+
if (entry.stackql_object_key && verb === 'get') {
|
|
304
|
+
methodEntry.response.objectKey = entry.stackql_object_key;
|
|
305
|
+
}
|
|
306
|
+
|
|
237
307
|
resources[resource].methods[method] = methodEntry;
|
|
238
308
|
if (sqlverb && sqlverb === 'exec') {
|
|
239
309
|
logger.info(`exec method skipped: ${resource}.${method}`);
|
|
@@ -247,11 +317,14 @@ export async function generate(options) {
|
|
|
247
317
|
}
|
|
248
318
|
}
|
|
249
319
|
|
|
320
|
+
// Sort operations by specificity before injecting into spec
|
|
321
|
+
const sortedResources = sortOperationsBySpecificity(resources, spec);
|
|
322
|
+
|
|
250
323
|
// Inject into spec
|
|
251
324
|
if (!spec.components) {
|
|
252
325
|
spec.components = {};
|
|
253
326
|
}
|
|
254
|
-
spec.components['x-stackQL-resources'] =
|
|
327
|
+
spec.components['x-stackQL-resources'] = sortedResources;
|
|
255
328
|
|
|
256
329
|
// Inject servers if provided
|
|
257
330
|
if (servers) {
|
package/src/providerdev/split.js
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
|
|
12
12
|
// Constants
|
|
13
13
|
const OPERATIONS = ["get", "post", "put", "delete", "patch", "options", "head", "trace"];
|
|
14
|
-
const NON_OPERATIONS = ["parameters", "servers", "summary", "description"];
|
|
15
14
|
const COMPONENTS_CHILDREN = ["schemas", "responses", "parameters", "examples", "requestBodies", "headers", "securitySchemes", "links", "callbacks"];
|
|
16
15
|
|
|
17
16
|
/**
|
|
@@ -42,9 +41,10 @@ function isOperationExcluded(exclude, opItem) {
|
|
|
42
41
|
* @param {string} svcDiscriminator - Service discriminator
|
|
43
42
|
* @param {Object[]} allTags - All tags from API doc
|
|
44
43
|
* @param {boolean} debug - Debug flag
|
|
44
|
+
* @param {Object} svcNameOverrides - Service name overrides
|
|
45
45
|
* @returns {[string, string]} - [service name, service description]
|
|
46
46
|
*/
|
|
47
|
-
function retServiceNameAndDesc(providerName, opItem, pathKey, svcDiscriminator, allTags, debug) {
|
|
47
|
+
function retServiceNameAndDesc(providerName, opItem, pathKey, svcDiscriminator, allTags, debug, svcNameOverrides) {
|
|
48
48
|
let service = "default";
|
|
49
49
|
let serviceDesc = `${providerName} API`;
|
|
50
50
|
|
|
@@ -84,9 +84,25 @@ function retServiceNameAndDesc(providerName, opItem, pathKey, svcDiscriminator,
|
|
|
84
84
|
return ["skip", ""];
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// Apply service name overrides if present
|
|
88
|
+
if (svcNameOverrides && svcNameOverrides[service]) {
|
|
89
|
+
const newName = svcNameOverrides[service];
|
|
90
|
+
if (debug) {
|
|
91
|
+
logger.debug(`Overriding service name: ${service} -> ${newName}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Update service description for path-based services
|
|
95
|
+
if (svcDiscriminator === "path") {
|
|
96
|
+
serviceDesc = `${providerName} ${newName} API`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
service = newName;
|
|
100
|
+
}
|
|
101
|
+
|
|
87
102
|
return [service, serviceDesc];
|
|
88
103
|
}
|
|
89
104
|
|
|
105
|
+
|
|
90
106
|
/**
|
|
91
107
|
* Initialize service map
|
|
92
108
|
* @param {Object} services - Services map
|
|
@@ -192,43 +208,6 @@ function getPathLevelRefs(pathItem) {
|
|
|
192
208
|
return refs;
|
|
193
209
|
}
|
|
194
210
|
|
|
195
|
-
/**
|
|
196
|
-
* Add referenced components to service
|
|
197
|
-
* @param {Set<string>} refs - Set of refs
|
|
198
|
-
* @param {Object} service - Service object
|
|
199
|
-
* @param {Object} components - Components from API doc
|
|
200
|
-
* @param {boolean} debug - Debug flag
|
|
201
|
-
*/
|
|
202
|
-
function addRefsToComponents(refs, service, components, debug) {
|
|
203
|
-
for (const ref of refs) {
|
|
204
|
-
const parts = ref.split('/');
|
|
205
|
-
|
|
206
|
-
// Only process refs that point to components
|
|
207
|
-
if (parts.length >= 4 && parts[1] === "components") {
|
|
208
|
-
const componentType = parts[2];
|
|
209
|
-
const componentName = parts[3];
|
|
210
|
-
|
|
211
|
-
// Check if component type exists in service
|
|
212
|
-
if (!service.components[componentType]) {
|
|
213
|
-
service.components[componentType] = {};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Skip if component already added
|
|
217
|
-
if (service.components[componentType][componentName]) {
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Add component if it exists in source document
|
|
222
|
-
if (components[componentType] && components[componentType][componentName]) {
|
|
223
|
-
service.components[componentType][componentName] = components[componentType][componentName];
|
|
224
|
-
if (debug) {
|
|
225
|
-
logger.debug(`Added component ${componentType}/${componentName}`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
211
|
/**
|
|
233
212
|
* Add missing type: object to schema objects
|
|
234
213
|
* @param {any} obj - Object to process
|
|
@@ -338,7 +317,8 @@ export async function split(options) {
|
|
|
338
317
|
svcDiscriminator = "tag",
|
|
339
318
|
exclude = null,
|
|
340
319
|
overwrite = true,
|
|
341
|
-
verbose = false
|
|
320
|
+
verbose = false,
|
|
321
|
+
svcNameOverrides = {} // Add this new parameter with default empty object
|
|
342
322
|
} = options;
|
|
343
323
|
|
|
344
324
|
// Setup logging based on verbosity
|
|
@@ -351,6 +331,13 @@ export async function split(options) {
|
|
|
351
331
|
logger.info(`Output: ${outputDir}`);
|
|
352
332
|
logger.info(`Service Discriminator: ${svcDiscriminator}`);
|
|
353
333
|
|
|
334
|
+
if (Object.keys(svcNameOverrides).length > 0) {
|
|
335
|
+
logger.info(`Using ${Object.keys(svcNameOverrides).length} service name overrides`);
|
|
336
|
+
if (verbose) {
|
|
337
|
+
logger.debug(`Service name overrides: ${JSON.stringify(svcNameOverrides, null, 2)}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
354
341
|
// Process exclude list
|
|
355
342
|
const excludeList = exclude ? exclude.split(",") : [];
|
|
356
343
|
|
|
@@ -409,12 +396,11 @@ export async function split(options) {
|
|
|
409
396
|
continue;
|
|
410
397
|
}
|
|
411
398
|
|
|
412
|
-
// Determine service name
|
|
413
399
|
const [service, serviceDesc] = retServiceNameAndDesc(
|
|
414
400
|
providerName, opItem, pathKey, svcDiscriminator,
|
|
415
|
-
apiDocObj.tags || [], verbose
|
|
416
|
-
);
|
|
417
|
-
|
|
401
|
+
apiDocObj.tags || [], verbose, svcNameOverrides
|
|
402
|
+
);
|
|
403
|
+
|
|
418
404
|
// Skip if service is marked to skip
|
|
419
405
|
if (service === 'skip') {
|
|
420
406
|
logger.warn(`⭐️ Skipping service: ${service}`);
|