@stackql/provider-utils 0.3.7 → 0.3.9
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/package.json +1 -1
- package/src/providerdev/analyze.js +7 -1
- package/src/providerdev/split.js +136 -94
package/package.json
CHANGED
|
@@ -172,12 +172,18 @@ export async function analyze(options) {
|
|
|
172
172
|
const filepath = path.join(inputDir, filename);
|
|
173
173
|
const spec = loadSpec(filepath);
|
|
174
174
|
|
|
175
|
+
const relevantVerbs = ["get", "put", "post", "patch", "delete"];
|
|
176
|
+
|
|
175
177
|
for (const [pathKey, pathItem] of Object.entries(spec.paths || {})) {
|
|
176
178
|
for (const [verb, operation] of Object.entries(pathItem)) {
|
|
177
179
|
if (typeof operation !== 'object' || operation === null) {
|
|
178
180
|
continue;
|
|
179
181
|
}
|
|
180
|
-
|
|
182
|
+
if(!relevantVerbs.includes(verb)) {
|
|
183
|
+
logger.info(`Skipping irrelevant operation: ${verb}`);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
181
187
|
// Then in the operation processing loop:
|
|
182
188
|
const operationId = operation.operationId || '';
|
|
183
189
|
// Check if operation is already mapped in CSV
|
package/src/providerdev/split.js
CHANGED
|
@@ -154,7 +154,7 @@ function getAllRefs(obj) {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
* Extract all $ref values from path-level parameters
|
|
157
|
+
* Extract all $ref values from path-level parameters and non-operation elements
|
|
158
158
|
* @param {Object} pathItem - Path item from OpenAPI doc
|
|
159
159
|
* @returns {Set<string>} - Set of refs
|
|
160
160
|
*/
|
|
@@ -166,15 +166,13 @@ function getPathLevelRefs(pathItem) {
|
|
|
166
166
|
for (const param of pathItem.parameters) {
|
|
167
167
|
if (param.$ref) {
|
|
168
168
|
refs.add(param.$ref);
|
|
169
|
-
if (param.$ref.includes('/parameters/')) {
|
|
170
|
-
// Make sure we catch all parameters, especially path parameters
|
|
171
|
-
const paramName = param.$ref.split('/').pop();
|
|
172
|
-
if (paramName) {
|
|
173
|
-
refs.add(`#/components/parameters/${paramName}`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
169
|
} else if (typeof param === 'object') {
|
|
177
170
|
// Extract refs from schema if present
|
|
171
|
+
if (param.schema && param.schema.$ref) {
|
|
172
|
+
refs.add(param.schema.$ref);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Also get all nested refs in the parameter object
|
|
178
176
|
for (const ref of getAllRefs(param)) {
|
|
179
177
|
refs.add(ref);
|
|
180
178
|
}
|
|
@@ -182,6 +180,15 @@ function getPathLevelRefs(pathItem) {
|
|
|
182
180
|
}
|
|
183
181
|
}
|
|
184
182
|
|
|
183
|
+
// Also check other non-operation properties for refs
|
|
184
|
+
for (const key in pathItem) {
|
|
185
|
+
if (!OPERATIONS.includes(key)) {
|
|
186
|
+
for (const ref of getAllRefs(pathItem[key])) {
|
|
187
|
+
refs.add(ref);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
185
192
|
return refs;
|
|
186
193
|
}
|
|
187
194
|
|
|
@@ -251,6 +258,73 @@ function addMissingObjectTypes(obj) {
|
|
|
251
258
|
return obj;
|
|
252
259
|
}
|
|
253
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Recursively resolve and add all references to service components
|
|
263
|
+
* @param {Set<string>} refs - Set of references to resolve
|
|
264
|
+
* @param {Object} service - Service object to add components to
|
|
265
|
+
* @param {Object} components - Source components from API doc
|
|
266
|
+
* @param {boolean} debug - Debug flag
|
|
267
|
+
* @param {Set<string>} processed - Set of already processed refs (to prevent infinite recursion)
|
|
268
|
+
*/
|
|
269
|
+
function resolveReferences(refs, service, components, debug, processed = new Set()) {
|
|
270
|
+
let newRefs = new Set();
|
|
271
|
+
|
|
272
|
+
for (const ref of refs) {
|
|
273
|
+
// Skip if already processed
|
|
274
|
+
if (processed.has(ref)) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
processed.add(ref);
|
|
279
|
+
|
|
280
|
+
const parts = ref.split('/');
|
|
281
|
+
|
|
282
|
+
// Only process refs that point to components
|
|
283
|
+
if (parts.length >= 4 && parts[1] === "components") {
|
|
284
|
+
const componentType = parts[2];
|
|
285
|
+
const componentName = parts[3];
|
|
286
|
+
|
|
287
|
+
// Check if component type exists in service
|
|
288
|
+
if (!service.components[componentType]) {
|
|
289
|
+
service.components[componentType] = {};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Skip if component already added
|
|
293
|
+
if (service.components[componentType][componentName]) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Add component if it exists in source document
|
|
298
|
+
if (components[componentType] && components[componentType][componentName]) {
|
|
299
|
+
service.components[componentType][componentName] =
|
|
300
|
+
JSON.parse(JSON.stringify(components[componentType][componentName]));
|
|
301
|
+
|
|
302
|
+
if (debug) {
|
|
303
|
+
logger.debug(`Added component ${componentType}/${componentName}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Find all refs in the newly added component
|
|
307
|
+
const componentRefs = getAllRefs(service.components[componentType][componentName]);
|
|
308
|
+
for (const cRef of componentRefs) {
|
|
309
|
+
if (!processed.has(cRef)) {
|
|
310
|
+
newRefs.add(cRef);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} else if (debug) {
|
|
314
|
+
logger.debug(`WARNING: Could not find component ${componentType}/${componentName}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// If we found new refs, resolve them too (recursively)
|
|
320
|
+
if (newRefs.size > 0) {
|
|
321
|
+
if (debug) {
|
|
322
|
+
logger.debug(`Found ${newRefs.size} additional refs to resolve`);
|
|
323
|
+
}
|
|
324
|
+
resolveReferences(newRefs, service, components, debug, processed);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
254
328
|
/**
|
|
255
329
|
* Split OpenAPI document into service-specific files
|
|
256
330
|
* @param {Object} options - Options for splitting
|
|
@@ -302,7 +376,7 @@ export async function split(options) {
|
|
|
302
376
|
const services = {};
|
|
303
377
|
let opCounter = 0;
|
|
304
378
|
|
|
305
|
-
//
|
|
379
|
+
// First pass: identify all services and collect operations
|
|
306
380
|
for (const [pathKey, pathItem] of Object.entries(apiPaths)) {
|
|
307
381
|
if (verbose) {
|
|
308
382
|
logger.debug(`Processing path ${pathKey}`);
|
|
@@ -312,6 +386,9 @@ export async function split(options) {
|
|
|
312
386
|
continue;
|
|
313
387
|
}
|
|
314
388
|
|
|
389
|
+
// Collect all services that use this path
|
|
390
|
+
const pathServices = new Set();
|
|
391
|
+
|
|
315
392
|
// Process each operation (HTTP verb)
|
|
316
393
|
for (const [verbKey, opItem] of Object.entries(pathItem)) {
|
|
317
394
|
if (!OPERATIONS.includes(verbKey) || !opItem) {
|
|
@@ -328,7 +405,7 @@ export async function split(options) {
|
|
|
328
405
|
}
|
|
329
406
|
|
|
330
407
|
// Skip excluded operations
|
|
331
|
-
if (isOperationExcluded(excludeList, opItem
|
|
408
|
+
if (isOperationExcluded(excludeList, opItem)) {
|
|
332
409
|
continue;
|
|
333
410
|
}
|
|
334
411
|
|
|
@@ -344,6 +421,8 @@ export async function split(options) {
|
|
|
344
421
|
continue;
|
|
345
422
|
}
|
|
346
423
|
|
|
424
|
+
pathServices.add(service);
|
|
425
|
+
|
|
347
426
|
if (verbose) {
|
|
348
427
|
logger.debug(`Service name: ${service}`);
|
|
349
428
|
logger.debug(`Service desc: ${serviceDesc}`);
|
|
@@ -375,104 +454,58 @@ export async function split(options) {
|
|
|
375
454
|
opItem['x-github'].subcategory
|
|
376
455
|
);
|
|
377
456
|
}
|
|
378
|
-
|
|
379
|
-
// Get all refs for operation
|
|
380
|
-
const opRefs = getAllRefs(opItem);
|
|
381
|
-
|
|
382
|
-
if (verbose) {
|
|
383
|
-
logger.debug(`Found ${opRefs.size} refs for ${service}`);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Add refs to components
|
|
387
|
-
addRefsToComponents(opRefs, services[service], apiDocObj.components || {}, verbose);
|
|
388
|
-
|
|
389
|
-
// Get internal refs
|
|
390
|
-
for (let i = 0; i < 3; i++) { // Internal ref depth
|
|
391
|
-
const intRefs = getAllRefs(services[service].components);
|
|
392
|
-
if (verbose) {
|
|
393
|
-
logger.debug(`Found ${intRefs.size} INTERNAL refs for service ${service}`);
|
|
394
|
-
}
|
|
395
|
-
addRefsToComponents(intRefs, services[service], apiDocObj.components || {}, verbose);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Get deeply nested schema refs
|
|
399
|
-
for (let i = 0; i < 10; i++) { // Schema max ref depth
|
|
400
|
-
const intRefs = getAllRefs(services[service].components);
|
|
401
|
-
// Filter refs that are already in service components
|
|
402
|
-
const filteredRefs = new Set();
|
|
403
|
-
for (const ref of intRefs) {
|
|
404
|
-
const parts = ref.split('/');
|
|
405
|
-
if (parts.length >= 4 && parts[1] === "components" && parts[2] === "schemas" &&
|
|
406
|
-
!services[service].components.schemas[parts[3]]) {
|
|
407
|
-
filteredRefs.add(ref);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (verbose) {
|
|
412
|
-
logger.debug(`Found ${filteredRefs.size} INTERNAL schema refs for service ${service}`);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (filteredRefs.size > 0) {
|
|
416
|
-
if (verbose) {
|
|
417
|
-
logger.debug(`Adding ${filteredRefs.size} INTERNAL schema refs for service ${service}`);
|
|
418
|
-
}
|
|
419
|
-
addRefsToComponents(filteredRefs, services[service], apiDocObj.components || {}, verbose);
|
|
420
|
-
} else {
|
|
421
|
-
if (verbose) {
|
|
422
|
-
logger.debug(`Exiting INTERNAL schema refs for ${service}`);
|
|
423
|
-
}
|
|
424
|
-
break;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
457
|
}
|
|
428
458
|
|
|
429
|
-
//
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
// Copy path-level parameters if they exist
|
|
440
|
-
if (pathItem.parameters) {
|
|
441
|
-
services[svcName].paths[pathKey].parameters = pathItem.parameters;
|
|
459
|
+
// For each service that uses this path, add path-level parameters
|
|
460
|
+
for (const service of pathServices) {
|
|
461
|
+
// Copy non-operation elements (like parameters) to service paths
|
|
462
|
+
for (const key in pathItem) {
|
|
463
|
+
if (!OPERATIONS.includes(key)) {
|
|
464
|
+
if (!services[service].paths[pathKey]) {
|
|
465
|
+
services[service].paths[pathKey] = {};
|
|
466
|
+
}
|
|
467
|
+
services[service].paths[pathKey][key] = pathItem[key];
|
|
442
468
|
}
|
|
443
|
-
|
|
444
|
-
// Add references from path-level parameters
|
|
445
|
-
addRefsToComponents(pathRefs, services[svcName], apiDocObj.components || {}, verbose);
|
|
446
469
|
}
|
|
447
470
|
}
|
|
448
471
|
}
|
|
449
472
|
|
|
450
|
-
//
|
|
473
|
+
// Second pass: collect all references for each service
|
|
451
474
|
for (const service in services) {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
475
|
+
if (verbose) {
|
|
476
|
+
logger.debug(`Collecting references for service ${service}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Get all refs from all operations in this service
|
|
480
|
+
const allRefs = new Set();
|
|
481
|
+
|
|
482
|
+
// Collect refs from paths
|
|
483
|
+
for (const pathKey in services[service].paths) {
|
|
484
|
+
const pathItem = services[service].paths[pathKey];
|
|
485
|
+
|
|
486
|
+
// Get refs from path-level parameters
|
|
487
|
+
const pathRefs = getPathLevelRefs(pathItem);
|
|
488
|
+
for (const ref of pathRefs) {
|
|
489
|
+
allRefs.add(ref);
|
|
455
490
|
}
|
|
456
491
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if (verbose) {
|
|
464
|
-
logger.debug(`Adding ${nonOp} to ${service} for path ${pathKey}`);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Special case for parameters
|
|
468
|
-
if (nonOp === 'parameters') {
|
|
469
|
-
for (const verbKey in services[service].paths[pathKey]) {
|
|
470
|
-
services[service].paths[pathKey][verbKey].parameters = apiPaths[pathKey].parameters;
|
|
471
|
-
}
|
|
492
|
+
// Get refs from operations
|
|
493
|
+
for (const verbKey in pathItem) {
|
|
494
|
+
if (OPERATIONS.includes(verbKey)) {
|
|
495
|
+
const opRefs = getAllRefs(pathItem[verbKey]);
|
|
496
|
+
for (const ref of opRefs) {
|
|
497
|
+
allRefs.add(ref);
|
|
472
498
|
}
|
|
473
499
|
}
|
|
474
500
|
}
|
|
475
501
|
}
|
|
502
|
+
|
|
503
|
+
if (verbose) {
|
|
504
|
+
logger.debug(`Found ${allRefs.size} total refs for service ${service}`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Resolve all references recursively
|
|
508
|
+
resolveReferences(allRefs, services[service], apiDocObj.components || {}, verbose);
|
|
476
509
|
}
|
|
477
510
|
|
|
478
511
|
// Update path param names (replace hyphens with underscores)
|
|
@@ -524,6 +557,15 @@ export async function split(options) {
|
|
|
524
557
|
services[service].components = addMissingObjectTypes(services[service].components);
|
|
525
558
|
}
|
|
526
559
|
|
|
560
|
+
// Cleanup empty components
|
|
561
|
+
for (const service in services) {
|
|
562
|
+
for (const componentType in services[service].components) {
|
|
563
|
+
if (Object.keys(services[service].components[componentType]).length === 0) {
|
|
564
|
+
delete services[service].components[componentType];
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
527
569
|
// Write out service docs
|
|
528
570
|
for (const service in services) {
|
|
529
571
|
logger.info(`✅ Writing out OpenAPI doc for [${service}]`);
|