@@ -5,14 +5,21 @@ import type {
55 VersionType ,
66} from '@changesets/types'
77import type { Gitlab } from '@gitbeaker/core'
8- import type { MergeRequestChangesSchema } from '@gitbeaker/rest'
8+ import type {
9+ DiscussionNoteSchema ,
10+ DiscussionSchema ,
11+ MergeRequestChangesSchema ,
12+ MergeRequestNoteSchema ,
13+ NoteSchema ,
14+ } from '@gitbeaker/rest'
915import { captureException } from '@sentry/node'
1016import { humanId } from 'human-id'
1117import { markdownTable } from 'markdown-table'
1218
1319import * as context from './context.js'
1420import { env } from './env.js'
1521import { getChangedPackages } from './get-changed-packages.js'
22+ import type { LooseString } from './types.js'
1623import { getUsername } from './utils.js'
1724
1825import { createApi } from './index.js'
@@ -104,32 +111,89 @@ ${changedPackages.map(x => `"${x}": patch`).join('\n')}
104111${ title }
105112` )
106113
107- const getNoteInfo = ( api : Gitlab , mrIid : number | string ) =>
108- api . MergeRequestDiscussions . all ( context . projectId , mrIid ) . then (
109- async discussions => {
110- for ( const discussion of discussions ) {
111- if ( ! discussion . notes ) {
112- continue
114+ const isMrNote = (
115+ discussionOrNote : DiscussionSchema | MergeRequestNoteSchema ,
116+ ) : discussionOrNote is MergeRequestNoteSchema =>
117+ 'noteable_type' in discussionOrNote &&
118+ discussionOrNote . noteable_type === 'MergeRequest'
119+
120+ const RANDOM_BOT_NAME_PATTERN = / ^ ( (?: p r o j e c t | g r o u p ) _ \d + _ b o t \w * ) _ [ \d a - z ] + $ / i
121+
122+ const isChangesetBotNote = (
123+ note : DiscussionNoteSchema | NoteSchema ,
124+ username : string ,
125+ random ?: boolean ,
126+ ) =>
127+ ( note . author . username === username ||
128+ ( random &&
129+ note . author . username . match ( RANDOM_BOT_NAME_PATTERN ) ?. [ 1 ] === username ) ) &&
130+ // We need to ensure the note is generated by us, but we don't have an app bot like GitHub
131+ // @see https://github.com/apps/changeset-bot
132+ note . body . includes ( generatedByBotNote )
133+
134+ async function getNoteInfo (
135+ api : Gitlab ,
136+ mrIid : number | string ,
137+ commentType : LooseString < 'discussion' > ,
138+ random ?: boolean ,
139+ ) : Promise < { discussionId : string ; noteId : number } | null | undefined >
140+ async function getNoteInfo (
141+ api : Gitlab ,
142+ mrIid : number | string ,
143+ commentType : LooseString < 'note' > ,
144+ random ?: boolean ,
145+ ) : Promise < { noteId : number } | null | undefined >
146+ async function getNoteInfo (
147+ api : Gitlab ,
148+ mrIid : number | string ,
149+ commentType : LooseString < 'discussion' | 'note' > ,
150+ random ?: boolean ,
151+ ) : Promise <
152+ | { discussionId : string ; noteId : number }
153+ | { noteId : number }
154+ | null
155+ | undefined
156+ > {
157+ const discussionOrNotes = await ( commentType === 'discussion'
158+ ? api . MergeRequestDiscussions . all ( context . projectId , mrIid )
159+ : api . MergeRequestNotes . all ( context . projectId , + mrIid ) )
160+
161+ const username = await getUsername ( api )
162+
163+ for ( const discussionOrNote of discussionOrNotes ) {
164+ if ( isMrNote ( discussionOrNote ) ) {
165+ if ( isChangesetBotNote ( discussionOrNote , username , random ) ) {
166+ return {
167+ noteId : discussionOrNote . id ,
113168 }
169+ }
170+ continue
171+ }
114172
115- const username = await getUsername ( api )
116- const changesetBotNote = discussion . notes . find (
117- note =>
118- note . author . username === username &&
119- // We need to ensure the note is generated by us, but we don't have an app bot like GitHub
120- // @see https://github.com/apps/changeset-bot
121- note . body . includes ( generatedByBotNote ) ,
122- )
173+ if ( ! discussionOrNote . notes ) {
174+ continue
175+ }
123176
124- if ( changesetBotNote ) {
125- return {
126- discussionId : discussion . id ,
127- noteId : changesetBotNote . id ,
128- }
129- }
177+ const changesetBotNote = discussionOrNote . notes . find ( note =>
178+ isChangesetBotNote ( note , username ) ,
179+ )
180+
181+ if ( changesetBotNote ) {
182+ return {
183+ discussionId : discussionOrNote . id ,
184+ noteId : changesetBotNote . id ,
130185 }
131- } ,
132- )
186+ }
187+ }
188+
189+ /**
190+ * The `username` used for commenting could be random, if we haven't tested the random `username`, then test it
191+ *
192+ * @see https://docs.gitlab.com/ee/development/internal_users.html
193+ * @see https://github.com/un-ts/changesets-gitlab/issues/145#issuecomment-1860610958
194+ */
195+ return random ? null : getNoteInfo ( api , mrIid , commentType , true )
196+ }
133197
134198const hasChangesetBeenAdded = async (
135199 changedFilesPromise : Promise < MergeRequestChangesSchema > ,
@@ -176,7 +240,7 @@ export const comment = async () => {
176240
177241 const [ noteInfo , hasChangeset , { changedPackages, releasePlan } ] =
178242 await Promise . all ( [
179- getNoteInfo ( api , mrIid ) ,
243+ getNoteInfo ( api , mrIid , GITLAB_COMMENT_TYPE ) ,
180244 hasChangesetBeenAdded ( changedFilesPromise ) ,
181245 getChangedPackages ( {
182246 changedFiles : changedFilesPromise . then ( x =>
@@ -217,42 +281,44 @@ export const comment = async () => {
217281 : getAbsentMessage ( latestCommitSha , addChangesetUrl , releasePlan ) ) +
218282 errFromFetchingChangedFiles
219283
220- if ( GITLAB_COMMENT_TYPE === 'discussion' ) {
221- if ( noteInfo ) {
222- return api . MergeRequestDiscussions . editNote (
284+ switch ( GITLAB_COMMENT_TYPE ) {
285+ case 'discussion' : {
286+ if ( noteInfo ) {
287+ return api . MergeRequestDiscussions . editNote (
288+ context . projectId ,
289+ mrIid ,
290+ noteInfo . discussionId ,
291+ noteInfo . noteId ,
292+ {
293+ body : prComment ,
294+ } ,
295+ )
296+ }
297+
298+ return api . MergeRequestDiscussions . create (
223299 context . projectId ,
224300 mrIid ,
225- noteInfo . discussionId ,
226- noteInfo . noteId ,
227- {
228- body : prComment ,
229- } ,
301+ prComment ,
230302 )
231303 }
304+ case 'note' : {
305+ if ( noteInfo ) {
306+ return api . MergeRequestNotes . edit (
307+ context . projectId ,
308+ mrIid ,
309+ noteInfo . noteId ,
310+ { body : prComment } ,
311+ )
312+ }
232313
233- return api . MergeRequestDiscussions . create (
234- context . projectId ,
235- mrIid ,
236- prComment ,
237- )
238- }
239-
240- if ( GITLAB_COMMENT_TYPE === 'note' ) {
241- if ( noteInfo ) {
242- return api . MergeRequestNotes . edit (
243- context . projectId ,
244- mrIid ,
245- noteInfo . noteId ,
246- { body : prComment } ,
314+ return api . MergeRequestNotes . create ( context . projectId , mrIid , prComment )
315+ }
316+ default : {
317+ throw new Error (
318+ `Invalid comment type "${ GITLAB_COMMENT_TYPE } ", should be "discussion" or "note"` ,
247319 )
248320 }
249-
250- return api . MergeRequestNotes . create ( context . projectId , mrIid , prComment )
251321 }
252-
253- throw new Error (
254- `Invalid comment type "${ GITLAB_COMMENT_TYPE } ", should be "discussion" or "note"` ,
255- )
256322 } catch ( err : unknown ) {
257323 console . error ( err )
258324 throw err
0 commit comments