@@ -164,6 +164,107 @@ function parseCommaList(value: string | undefined): string[] | undefined {
164164 . filter ( Boolean ) ;
165165}
166166
167+ /** Paginate through all tools and return the full list. */
168+ async function fetchAllTools ( ) : Promise < Tool [ ] > {
169+ const tools : Tool [ ] = [ ] ;
170+ let cursor : string | undefined ;
171+ do {
172+ const resp = await ToolsService . listTools ( cursor , 100 ) ;
173+ tools . push ( ...resp . data ) ;
174+ cursor = resp . pagination ?. cursor ;
175+ } while ( cursor ) ;
176+ return tools ;
177+ }
178+
179+ /**
180+ * Build the SearchRepositoryIssuesBody from parsed CLI options.
181+ * Resolves tool names/UUIDs via the Codacy API when --tools is provided.
182+ */
183+ async function buildFilterBody ( opts : Record < string , any > ) : Promise < SearchRepositoryIssuesBody > {
184+ const body : SearchRepositoryIssuesBody = { } ;
185+
186+ if ( opts . branch ) body . branchName = opts . branch ;
187+
188+ const patterns = parseCommaList ( opts . patterns ) ;
189+ if ( patterns ) body . patternIds = patterns ;
190+
191+ const severity = parseCommaList ( opts . severities ) ;
192+ if ( severity ) body . levels = severity . map ( normalizeSeverity ) ;
193+
194+ const category = parseCommaList ( opts . categories ) ;
195+ if ( category ) body . categories = category . map ( normalizeCategory ) ;
196+
197+ const language = parseCommaList ( opts . languages ) ;
198+ if ( language ) body . languages = language ;
199+
200+ const tags = parseCommaList ( opts . tags ) ;
201+ if ( tags ) body . tags = tags ;
202+
203+ const author = parseCommaList ( opts . authors ) ;
204+ if ( author ) body . authorEmails = author ;
205+
206+ // --false-positives and --bulk-ignore both restrict the API query to FP issues only
207+ if ( opts . falsePositives || opts . bulkIgnore ) body . onlyPotentialFalsePositives = true ;
208+
209+ const toolInputs = parseCommaList ( opts . tools ) ;
210+ if ( toolInputs ) body . toolUuids = await resolveToolUuids ( toolInputs , fetchAllTools ) ;
211+
212+ return body ;
213+ }
214+
215+ /**
216+ * Fetch every false positive issue (all pages) then ignore them in batches of
217+ * BULK_BATCH_SIZE. Prints progress via spinners and exits when done.
218+ */
219+ async function executeBulkIgnore (
220+ provider : string ,
221+ organization : string ,
222+ repository : string ,
223+ body : SearchRepositoryIssuesBody ,
224+ comment : string | undefined ,
225+ ) : Promise < void > {
226+ const fetchSpinner = ora ( "Fetching false positive issues..." ) . start ( ) ;
227+ const allIssues : CommitIssue [ ] = [ ] ;
228+ let cursor : string | undefined ;
229+
230+ do {
231+ const resp = await AnalysisService . searchRepositoryIssues (
232+ provider ,
233+ organization ,
234+ repository ,
235+ cursor ,
236+ 100 ,
237+ body ,
238+ ) ;
239+ allIssues . push ( ...resp . data ) ;
240+ cursor = resp . pagination ?. cursor ;
241+ } while ( cursor ) ;
242+
243+ fetchSpinner . stop ( ) ;
244+
245+ if ( allIssues . length === 0 ) {
246+ console . log ( ansis . green ( "No false positive issues found." ) ) ;
247+ return ;
248+ }
249+
250+ const count = allIssues . length ;
251+ const plural = count === 1 ? "" : "s" ;
252+ console . log ( `Found ${ ansis . bold ( String ( count ) ) } false positive issue${ plural } .` ) ;
253+
254+ const ignoreSpinner = ora ( `Ignoring ${ count } issue${ plural } ...` ) . start ( ) ;
255+ const issueIds = allIssues . map ( ( i ) => i . issueId ) ;
256+
257+ for ( let i = 0 ; i < issueIds . length ; i += BULK_BATCH_SIZE ) {
258+ await AnalysisService . bulkIgnoreIssues ( provider , organization , repository , {
259+ issueIds : issueIds . slice ( i , i + BULK_BATCH_SIZE ) ,
260+ reason : "FalsePositive" ,
261+ comment : comment || undefined ,
262+ } ) ;
263+ }
264+
265+ ignoreSpinner . succeed ( `Ignored ${ ansis . bold ( String ( count ) ) } false positive issue${ plural } .` ) ;
266+ }
267+
167268export function registerIssuesCommand ( program : Command ) {
168269 program
169270 . command ( "issues" )
@@ -190,7 +291,7 @@ export function registerIssuesCommand(program: Command) {
190291 . option ( "-O, --overview" , "show issue count totals instead of the issues list" )
191292 . option ( "-F, --false-positives" , "only show issues that are potential false positives" )
192293 . option ( "-I, --bulk-ignore" , "ignore all false positive issues matching the current filters" )
193- . option ( "-m, --comment <comment>" , "optional comment when using --bulk-ignore" )
294+ . option ( "-m, --ignore- comment <comment>" , "optional comment when using --bulk-ignore" )
194295 . addHelpText (
195296 "after" ,
196297 `
@@ -202,7 +303,7 @@ Examples:
202303 $ codacy issues gh my-org my-repo --limit 500
203304 $ codacy issues gh my-org my-repo --false-positives
204305 $ codacy issues gh my-org my-repo --bulk-ignore --branch main
205- $ codacy issues gh my-org my-repo --bulk-ignore --patterns security-rule --comment "Confirmed FPs"
306+ $ codacy issues gh my-org my-repo --bulk-ignore --patterns security-rule --ignore- comment "Confirmed FPs"
206307 $ codacy issues gh my-org my-repo --output json` ,
207308 )
208309 . action ( async function (
@@ -216,84 +317,15 @@ Examples:
216317 const opts = this . opts ( ) ;
217318 const format = getOutputFormat ( this ) ;
218319 const isOverview = ! ! opts . overview ;
219- const isBulkIgnore = ! ! opts . bulkIgnore ;
220-
221- // Build the shared filter body from CLI options
222- const body : SearchRepositoryIssuesBody = { } ;
223- if ( opts . branch ) body . branchName = opts . branch ;
224- const patterns = parseCommaList ( opts . patterns ) ;
225- if ( patterns ) body . patternIds = patterns ;
226- const severity = parseCommaList ( opts . severities ) ;
227- if ( severity ) body . levels = severity . map ( normalizeSeverity ) ;
228- const category = parseCommaList ( opts . categories ) ;
229- if ( category ) body . categories = category . map ( normalizeCategory ) ;
230- const language = parseCommaList ( opts . languages ) ;
231- if ( language ) body . languages = language ;
232- const tags = parseCommaList ( opts . tags ) ;
233- if ( tags ) body . tags = tags ;
234- const author = parseCommaList ( opts . authors ) ;
235- if ( author ) body . authorEmails = author ;
236- // --false-positives and --bulk-ignore both restrict the API query to FP issues only
237- if ( opts . falsePositives || isBulkIgnore ) body . onlyPotentialFalsePositives = true ;
238-
239- const toolInputs = parseCommaList ( opts . tools ) ;
240- if ( toolInputs ) {
241- body . toolUuids = await resolveToolUuids ( toolInputs , async ( ) => {
242- const tools : Tool [ ] = [ ] ;
243- let cursor : string | undefined ;
244- do {
245- const resp = await ToolsService . listTools ( cursor , 100 ) ;
246- tools . push ( ...resp . data ) ;
247- cursor = resp . pagination ?. cursor ;
248- } while ( cursor ) ;
249- return tools ;
250- } ) ;
251- }
252320
321+ const body = await buildFilterBody ( opts ) ;
253322 const limit = Math . min ( Math . max ( parseInt ( opts . limit , 10 ) || 100 , 1 ) , 1000 ) ;
254323
255- // --bulk-ignore: fetch all FP issues (all pages) then call bulkIgnoreIssues in batches
256- if ( isBulkIgnore ) {
257- const fetchSpinner = ora ( "Fetching false positive issues..." ) . start ( ) ;
258- const allIssues : CommitIssue [ ] = [ ] ;
259- let cursor : string | undefined ;
260-
261- do {
262- const resp = await AnalysisService . searchRepositoryIssues (
263- provider ,
264- organization ,
265- repository ,
266- cursor ,
267- 100 ,
268- body ,
269- ) ;
270- allIssues . push ( ...resp . data ) ;
271- cursor = resp . pagination ?. cursor ;
272- } while ( cursor ) ;
273-
274- fetchSpinner . stop ( ) ;
275-
276- if ( allIssues . length === 0 ) {
277- console . log ( ansis . green ( "No false positive issues found." ) ) ;
278- return ;
324+ if ( opts . bulkIgnore ) {
325+ if ( this . getOptionValueSource ( "limit" ) === "cli" ) {
326+ this . error ( "--limit cannot be used with --bulk-ignore; the bulk-ignore path always processes all matching issues" ) ;
279327 }
280-
281- const count = allIssues . length ;
282- const plural = count === 1 ? "" : "s" ;
283- console . log ( `Found ${ ansis . bold ( String ( count ) ) } false positive issue${ plural } .` ) ;
284-
285- const ignoreSpinner = ora ( `Ignoring ${ count } issue${ plural } ...` ) . start ( ) ;
286- const issueIds = allIssues . map ( ( i ) => i . issueId ) ;
287-
288- for ( let i = 0 ; i < issueIds . length ; i += BULK_BATCH_SIZE ) {
289- await AnalysisService . bulkIgnoreIssues ( provider , organization , repository , {
290- issueIds : issueIds . slice ( i , i + BULK_BATCH_SIZE ) ,
291- reason : "FalsePositive" ,
292- comment : opts . comment || undefined ,
293- } ) ;
294- }
295-
296- ignoreSpinner . succeed ( `Ignored ${ ansis . bold ( String ( count ) ) } false positive issue${ plural } .` ) ;
328+ await executeBulkIgnore ( provider , organization , repository , body , opts . ignoreComment ) ;
297329 return ;
298330 }
299331
0 commit comments