@@ -11,6 +11,7 @@ import type { IApimClient } from '../clients/iapim-client.js';
1111import type { IArtifactStore } from '../clients/iartifact-store.js' ;
1212import type { ApimServiceContext , ResourceDescriptor } from '../models/types.js' ;
1313import type { PublishConfig } from '../models/config.js' ;
14+ import * as yaml from 'js-yaml' ;
1415import { ResourceType } from '../models/resource-types.js' ;
1516import { publishResource , type ResourcePublishResult } from './resource-publisher.js' ;
1617import { runParallel } from '../lib/parallel-runner.js' ;
@@ -56,6 +57,15 @@ export async function publishApi(
5657 return rootResult ;
5758 }
5859
60+ if ( rootResult . specImported && rootResult . operationIdsWithNullDescription ?. length ) {
61+ await alignImportedOperationDescriptions (
62+ client ,
63+ context ,
64+ descriptor ,
65+ rootResult . operationIdsWithNullDescription
66+ ) ;
67+ }
68+
5969 // Step 2: Find and publish revisions in numeric order
6070 const publishedRevisionCount = await publishApiRevisions ( client , store , context , descriptor , config ) ;
6171
@@ -125,6 +135,7 @@ function getImportFormat(specFormat: string, _apiType?: string): string | undefi
125135interface RootApiResult {
126136 status : 'success' | 'skipped' ;
127137 specImported : boolean ;
138+ operationIdsWithNullDescription ?: string [ ] ;
128139 isCurrent ?: boolean ;
129140}
130141
@@ -162,6 +173,7 @@ async function publishRootApi(
162173
163174 // Try to read the specification file for this API
164175 let specImported = false ;
176+ let operationIdsWithNullDescription : string [ ] = [ ] ;
165177 const includeSpecification = options ?. includeSpecification ?? true ;
166178 const specResult = includeSpecification
167179 ? await store . readContent ( config . sourceDir , descriptor , 'specification' )
@@ -197,6 +209,14 @@ async function publishRootApi(
197209 value : specResult . content ,
198210 } ,
199211 } ;
212+
213+ if ( importFormat === 'openapi' || importFormat === 'openapi+json' ) {
214+ operationIdsWithNullDescription = getOpenApiOperationIdsWithNullDescription (
215+ specResult . content ,
216+ specResult . format
217+ ) ;
218+ }
219+
200220 specImported = true ;
201221 logger . info ( `Including ${ specResult . format } specification in API import for "${ getNamePart ( descriptor . nameParts , 0 ) } "` ) ;
202222 }
@@ -210,6 +230,7 @@ async function publishRootApi(
210230 status : 'success' ,
211231 action : 'put' ,
212232 specImported,
233+ operationIdsWithNullDescription,
213234 isCurrent,
214235 } ;
215236}
@@ -453,3 +474,91 @@ function getApiIsCurrent(json: Record<string, unknown>): boolean | undefined {
453474 const isCurrent = properties ?. isCurrent ;
454475 return typeof isCurrent === 'boolean' ? isCurrent : undefined ;
455476}
477+
478+ async function alignImportedOperationDescriptions (
479+ client : IApimClient ,
480+ context : ApimServiceContext ,
481+ apiDescriptor : ResourceDescriptor ,
482+ operationIdsWithNullDescription : string [ ]
483+ ) : Promise < void > {
484+ const apiName = getNamePart ( apiDescriptor . nameParts , 0 ) ;
485+
486+ for ( const operationName of operationIdsWithNullDescription ) {
487+ const operationDescriptor : ResourceDescriptor = {
488+ type : ResourceType . ApiOperation ,
489+ nameParts : [ apiName , operationName ] ,
490+ workspace : apiDescriptor . workspace ,
491+ } ;
492+
493+ const operation = await client . getResource ( context , operationDescriptor ) ;
494+ if ( ! operation ) {
495+ continue ;
496+ }
497+
498+ const props = operation . properties as Record < string , unknown > | undefined ;
499+ if ( ! props || props . description === null ) {
500+ continue ;
501+ }
502+
503+ await client . putResource ( context , operationDescriptor , {
504+ ...operation ,
505+ properties : {
506+ ...props ,
507+ description : null ,
508+ } ,
509+ } ) ;
510+ }
511+ }
512+
513+ function getOpenApiOperationIdsWithNullDescription (
514+ specContent : string ,
515+ specFormat ?: string
516+ ) : string [ ] {
517+ try {
518+ const parsed : unknown =
519+ specFormat === 'json'
520+ ? JSON . parse ( specContent )
521+ : yaml . load ( specContent ) ;
522+
523+ if ( ! parsed || typeof parsed !== 'object' ) {
524+ return [ ] ;
525+ }
526+
527+ const parsedRecord = parsed as Record < string , unknown > ;
528+ const pathsValue = parsedRecord . paths ;
529+ if ( ! pathsValue || typeof pathsValue !== 'object' ) {
530+ return [ ] ;
531+ }
532+
533+ const paths = pathsValue as Record < string , unknown > ;
534+
535+ const methods = new Set ( [ 'get' , 'put' , 'post' , 'delete' , 'options' , 'head' , 'patch' , 'trace' ] ) ;
536+ const operationIds = new Set < string > ( ) ;
537+
538+ for ( const pathItem of Object . values ( paths ) ) {
539+ if ( ! pathItem || typeof pathItem !== 'object' ) {
540+ continue ;
541+ }
542+
543+ for ( const [ methodName , operation ] of Object . entries ( pathItem as Record < string , unknown > ) ) {
544+ if ( ! methods . has ( methodName . toLowerCase ( ) ) || ! operation || typeof operation !== 'object' ) {
545+ continue ;
546+ }
547+
548+ const operationRecord = operation as Record < string , unknown > ;
549+ const operationId = operationRecord . operationId ;
550+ if ( typeof operationId !== 'string' || operationId . length === 0 ) {
551+ continue ;
552+ }
553+
554+ if ( ! Object . hasOwn ( operationRecord , 'description' ) || operationRecord . description == null ) {
555+ operationIds . add ( operationId ) ;
556+ }
557+ }
558+ }
559+
560+ return [ ...operationIds ] ;
561+ } catch {
562+ return [ ] ;
563+ }
564+ }
0 commit comments