@@ -1022,9 +1022,13 @@ export class PullRequestGenerator {
10221022 // Sanitize @mentions to avoid pinging real users in PRs
10231023 cleaned = this . sanitizeMentions ( cleaned )
10241024
1025- // Limit to reasonable length for PR body
1026- if ( cleaned . length > 1000 ) {
1027- cleaned = `${ cleaned . substring ( 0 , 1000 ) } ...\n\n*[View full release notes]*`
1025+ // Sanitize GitHub issue/PR references to prevent spam notifications
1026+ cleaned = this . sanitizeGitHubReferences ( cleaned )
1027+
1028+ // Limit to reasonable length for PR body (configurable, default: 1000)
1029+ const maxLength = this . config ?. releaseNotes ?. maxBodyLength ?? 1000
1030+ if ( cleaned . length > maxLength ) {
1031+ cleaned = `${ cleaned . substring ( 0 , maxLength ) } ...\n\n*[View full release notes]*`
10281032 }
10291033
10301034 return cleaned
@@ -1070,6 +1074,60 @@ export class PullRequestGenerator {
10701074 return protectedText
10711075 }
10721076
1077+ /**
1078+ * Sanitize GitHub issue/PR references to prevent spam notifications.
1079+ * Converts references like #123 and GitHub URLs into non-linking text.
1080+ * Examples:
1081+ * #123 -> `#123`
1082+ * fixes #456 -> fixes `#456`
1083+ * https://github.com/org/repo/pull/123 -> `org/repo#123`
1084+ * https://github.com/org/repo/issues/456 -> `org/repo#456`
1085+ */
1086+ private sanitizeGitHubReferences ( text : string ) : string {
1087+ // Check if sanitization is enabled (default: true)
1088+ if ( this . config ?. releaseNotes ?. sanitizeReferences === false ) {
1089+ return text
1090+ }
1091+
1092+ // Protect fenced code blocks and inline code by temporarily replacing them
1093+ const fences : string [ ] = [ ]
1094+ const inlineCode : string [ ] = [ ]
1095+
1096+ // Replace fenced code blocks ```...``` with placeholders
1097+ let protectedText = text . replace ( / ` ` ` [ \s \S ] * ?` ` ` / g, ( match ) => {
1098+ fences . push ( match )
1099+ return `__FENCE_PLACEHOLDER_${ fences . length - 1 } __`
1100+ } )
1101+
1102+ // Replace inline code `...` with placeholders
1103+ protectedText = protectedText . replace ( / ` [ ^ ` \n ] + ` / g, ( match ) => {
1104+ inlineCode . push ( match )
1105+ return `__INLINE_CODE_PLACEHOLDER_${ inlineCode . length - 1 } __`
1106+ } )
1107+
1108+ // Replace full GitHub issue/PR URLs with non-linking format
1109+ // https://github.com/org/repo/pull/123 -> `org/repo#123`
1110+ // https://github.com/org/repo/issues/456 -> `org/repo#456`
1111+ protectedText = protectedText . replace (
1112+ / h t t p s ? : \/ \/ g i t h u b \. c o m \/ ( [ ^ / ] + ) \/ ( [ ^ / ] + ) \/ (?: p u l l | i s s u e s ) \/ ( \d + ) / g,
1113+ ( _match , owner : string , repo : string , num : string ) => `\`${ owner } /${ repo } #${ num } \`` ,
1114+ )
1115+
1116+ // Replace standalone issue/PR references #123 with backtick-wrapped version
1117+ // This prevents GitHub from creating links/notifications
1118+ // Negative lookbehind: don't match if preceded by ` or alphanumeric (part of URL or already wrapped)
1119+ // Negative lookahead: don't match if followed by alphanumeric
1120+ protectedText = protectedText . replace ( / (?< ! [ ` \w ] ) # ( \d + ) (? ! \d ) / g, '`#$1`' )
1121+
1122+ // Restore inline code placeholders
1123+ protectedText = protectedText . replace ( / _ _ I N L I N E _ C O D E _ P L A C E H O L D E R _ ( \d + ) _ _ / g, ( _m , i : string ) => inlineCode [ Number ( i ) ] )
1124+
1125+ // Restore fenced code placeholders
1126+ protectedText = protectedText . replace ( / _ _ F E N C E _ P L A C E H O L D E R _ ( \d + ) _ _ / g, ( _m , i : string ) => fences [ Number ( i ) ] )
1127+
1128+ return protectedText
1129+ }
1130+
10731131 /**
10741132 * Generate custom PR templates
10751133 */
0 commit comments