@@ -29,6 +29,7 @@ import {
2929} from '../../types'
3030import { BUILD_TYPE , EMPTY_CHANGE_SUMMARY , MESSAGE_SEVERITY } from '../../consts'
3131import { convertToSlug , takeIfDefined } from '../../utils'
32+ import { normalizePath } from '../../utils/builder'
3233
3334export const totalChanges = ( changeSummary ?: ChangeSummary ) : number => {
3435 return changeSummary
@@ -96,31 +97,50 @@ export function getOperationsHashMapByApiType(
9697 const resolvedHashMap = { ...operationTypes . find ( ( { apiType : type } ) => type === currentApiType ) ?. operations || { } }
9798 const { buildType, currentGroup, previousGroup } = ctx . config
9899
99- if ( buildType !== BUILD_TYPE . PREFIX_GROUPS_CHANGELOG ) {
100- return resolvedHashMap
101- }
100+ // Handle prefix group changelog case
101+ if ( buildType === BUILD_TYPE . PREFIX_GROUPS_CHANGELOG ) {
102+ if ( ! currentGroup || ! previousGroup ) {
103+ ctx . notifications . push ( {
104+ severity : MESSAGE_SEVERITY . Warning ,
105+ message : `Build type is prefix group changelog, but one of the groups is not provided: currentGroup=${ currentGroup } , previousGroup=${ previousGroup } ` ,
106+ } )
107+ return resolvedHashMap
108+ }
109+
110+ const groupSlug = convertToSlug ( areOperationsFromCurrentVersion ? currentGroup : previousGroup )
111+ const newHashMap : ResolvedVersionOperationsHashMap = { }
112+
113+ // Process each operation
114+ for ( const [ operationId , dataHash ] of Object . entries ( resolvedHashMap ) ) {
115+ if ( operationId . startsWith ( groupSlug ) ) {
116+ const changedOperationId = operationId . substring ( groupSlug . length )
117+ newHashMap [ changedOperationId ] = dataHash
118+ operationIdentityMap [ changedOperationId ] = operationId
119+ }
120+ }
102121
103- if ( ! currentGroup || ! previousGroup ) {
104- ctx . notifications . push ( {
105- severity : MESSAGE_SEVERITY . Warning ,
106- message : `Build type is prefix group changelog, but one of the groups is not provided: currentGroup=${ currentGroup } , previousGroup=${ previousGroup } ` ,
107- } )
108- return resolvedHashMap
122+ return newHashMap
109123 }
110124
125+ // Handle path parameter normalization case
126+ const newHashMap : ResolvedVersionOperationsHashMap = { }
111127 for ( const [ operationId , dataHash ] of Object . entries ( resolvedHashMap ) ) {
112- Reflect . deleteProperty ( resolvedHashMap , operationId )
113-
114- const groupSlug = convertToSlug ( areOperationsFromCurrentVersion ? currentGroup : previousGroup )
115-
116- if ( operationId . startsWith ( groupSlug ) ) {
117- const changedOperationId = operationId . substring ( groupSlug . length )
118- resolvedHashMap [ changedOperationId ] = dataHash
119- operationIdentityMap [ changedOperationId ] = operationId
128+ // Get operation metadata to normalize the path
129+ const operation = operationTypes . find ( ( { apiType : type } ) => type === currentApiType )
130+ ?. operations_metadata ?. [ operationId ]
131+
132+ if ( operation ?. path && operation ?. method ) {
133+ const normalizedPath = normalizePath ( operation . path )
134+ const normalizedId = `${ operation . method . toLowerCase ( ) } -${ normalizedPath } `
135+ newHashMap [ normalizedId ] = dataHash
136+ operationIdentityMap [ normalizedId ] = operationId
137+ } else {
138+ newHashMap [ operationId ] = dataHash
139+ operationIdentityMap [ operationId ] = operationId
120140 }
121141 }
122142
123- return resolvedHashMap
143+ return newHashMap
124144}
125145
126146export function getOperationMetadata ( operation : ResolvedOperation ) : OperationChangesMetadata {
@@ -142,3 +162,26 @@ export function takeSubstringIf(condition: boolean, value: string, startIndex: n
142162
143163 return value . substring ( startIndex )
144164}
165+
166+ /**
167+ * Normalizes a path by replacing path parameters with a placeholder
168+ * regardless of the parameter name
169+ * e.g. /users/{userId}/posts/{postId} -> /users/{param}/posts/{param}
170+ */
171+ export function normalizePath ( path : string ) : string {
172+ return path . replace ( / \{ [ ^ } ] + \} / g, '{param}' )
173+ }
174+
175+ /**
176+ * Gets a normalized operation identifier that is resilient to path parameter renaming
177+ */
178+ export function getNormalizedOperationId ( operationId : string , metadata ?: { path ?: string , method ?: string } ) : string {
179+ // If no path/method metadata, return original ID
180+ if ( ! metadata ?. path || ! metadata ?. method ) {
181+ return operationId
182+ }
183+
184+ // Create normalized ID from method and normalized path using existing normalizePath utility
185+ const normalizedPath = normalizePath ( metadata . path )
186+ return `${ metadata . method . toLowerCase ( ) } -${ normalizedPath } `
187+ }
0 commit comments