1+ // Script to trigger CodeRabbit plan for beginner, intermediate, and advanced issues.
2+
3+ const CODERABBIT_MARKER = '<!-- CodeRabbit Plan Trigger -->' ;
4+
5+ async function triggerCodeRabbitPlan (
6+ github ,
7+ owner ,
8+ repo ,
9+ issue ,
10+ marker = CODERABBIT_MARKER ,
11+ isDryRun = false ,
12+ ) {
13+ const comment = `${ marker } @coderabbitai plan` ;
14+
15+ if ( isDryRun ) {
16+ console . log ( `[DRY RUN] Would trigger CodeRabbit plan for issue #${ issue . number } ` ) ;
17+ return true ;
18+ }
19+
20+ try {
21+ await github . rest . issues . createComment ( {
22+ owner,
23+ repo,
24+ issue_number : issue . number ,
25+ body : comment ,
26+ } ) ;
27+ console . log ( `Triggered CodeRabbit plan for issue #${ issue . number } ` ) ;
28+ return true ;
29+ } catch ( commentErr ) {
30+ console . log ( 'Failed to trigger CodeRabbit plan:' , {
31+ message : commentErr ?. message ,
32+ status : commentErr ?. status ,
33+ owner,
34+ repo,
35+ issueNumber : issue ?. number ,
36+ } ) ;
37+ return false ;
38+ }
39+ }
40+
41+ function hasBeginnerOrHigherLabel ( issue , label ) {
42+ // Check if issue has beginner, intermediate, or advanced label (case-insensitive).
43+ const allowed = [ 'beginner' , 'intermediate' , 'advanced' ] ;
44+
45+ const hasAllowedLabel = issue . labels ?. some ( ( l ) => allowed . includes ( l ?. name ?. toLowerCase ( ) ) ) ;
46+
47+ // Also check if newly added label is beginner/intermediate/advanced.
48+ const isNewLabelAllowed = allowed . includes ( label ?. name ?. toLowerCase ( ) ) ;
49+
50+ return hasAllowedLabel || isNewLabelAllowed ;
51+ }
52+
53+ async function hasExistingCodeRabbitPlan ( github , owner , repo , issueNumber ) {
54+ // Check for existing CodeRabbit plan comment (limited to first 500 comments).
55+ // Uses marker-based detection to avoid false positives from quoted text.
56+ try {
57+ const comments = [ ] ;
58+ const iterator = github . paginate . iterator ( github . rest . issues . listComments , {
59+ owner,
60+ repo,
61+ issue_number : issueNumber ,
62+ per_page : 100 ,
63+ } ) ;
64+
65+ let count = 0 ;
66+ for await ( const { data : page } of iterator ) {
67+ comments . push ( ...page ) ;
68+ count += page . length ;
69+ if ( count >= 500 ) break ; // Hard upper bound to prevent unbounded pagination.
70+ }
71+
72+ // Check for marker-based comment OR @coderabbitai plan text.
73+ return comments . some (
74+ ( c ) =>
75+ typeof c ?. body === 'string' &&
76+ ( c . body . includes ( CODERABBIT_MARKER ) || c . body . includes ( '@coderabbitai plan' ) ) ,
77+ ) ;
78+ } catch ( error ) {
79+ console . log ( 'Failed to check existing CodeRabbit plan comments:' , {
80+ message : error ?. message ,
81+ status : error ?. status ,
82+ owner,
83+ repo,
84+ issueNumber,
85+ } ) ;
86+ // Return false to allow plan trigger attempt (fail-open for better UX).
87+ return false ;
88+ }
89+ }
90+
91+ function logSummary ( owner , repo , issue ) {
92+ console . log ( '=== Summary ===' ) ;
93+ console . log ( `Repository: ${ owner } /${ repo } ` ) ;
94+ console . log ( `Issue Number: ${ issue . number } ` ) ;
95+ console . log ( `Issue Title: ${ issue . title || '(no title)' } ` ) ;
96+ console . log ( `Labels: ${ issue . labels ?. map ( ( l ) => l . name ) . join ( ', ' ) || 'none' } ` ) ;
97+ }
98+
99+ // Main workflow handler (default export for workflow usage).
100+ async function main ( { github, context } ) {
101+ try {
102+ const { owner, repo } = context . repo ;
103+ const { issue, label } = context . payload ;
104+
105+ // Validations.
106+ if ( ! issue ?. number ) return console . log ( 'No issue in payload' ) ;
107+
108+ if ( ! hasBeginnerOrHigherLabel ( issue , label ) ) {
109+ return console . log ( 'Issue does not have beginner/intermediate/advanced label' ) ;
110+ }
111+
112+ if ( await hasExistingCodeRabbitPlan ( github , owner , repo , issue . number ) ) {
113+ return console . log ( `CodeRabbit plan already triggered for #${ issue . number } ` ) ;
114+ }
115+
116+ // Check for dry run (default to true if not specified, for safety).
117+ const isDryRun = ( process . env . DRY_RUN || 'true' ) . toLowerCase ( ) === 'true' ;
118+
119+ // Post CodeRabbit plan trigger.
120+ await triggerCodeRabbitPlan ( github , owner , repo , issue , CODERABBIT_MARKER , isDryRun ) ;
121+
122+ logSummary ( owner , repo , issue ) ;
123+ } catch ( err ) {
124+ console . log ( 'Error:' , {
125+ message : err ?. message ,
126+ status : err ?. status ,
127+ owner : context ?. repo ?. owner ,
128+ repo : context ?. repo ?. repo ,
129+ issueNumber : context ?. payload ?. issue ?. number ,
130+ } ) ;
131+ }
132+ }
133+
134+ // Default export for workflow usage: await script({ github, context }).
135+ module . exports = main ;
136+
137+ // Named exports for reuse by other scripts.
138+ module . exports . triggerCodeRabbitPlan = triggerCodeRabbitPlan ;
139+ module . exports . hasExistingCodeRabbitPlan = hasExistingCodeRabbitPlan ;
140+ module . exports . CODERABBIT_MARKER = CODERABBIT_MARKER ;
0 commit comments