mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-02-01 22:48:03 +00:00
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
152 lines
3.9 KiB
JavaScript
152 lines
3.9 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
import { Octokit } from '@octokit/rest';
|
|
import yargs from 'yargs';
|
|
import { hideBin } from 'yargs/helpers';
|
|
import prompts from 'prompts';
|
|
|
|
if (!process.env.GITHUB_TOKEN) {
|
|
console.error('Error: GITHUB_TOKEN environment variable is required.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const argv = yargs(hideBin(process.argv))
|
|
.option('query', {
|
|
alias: 'q',
|
|
type: 'string',
|
|
description:
|
|
'Search query to find duplicate issues (e.g. "function response parts")',
|
|
demandOption: true,
|
|
})
|
|
.option('canonical', {
|
|
alias: 'c',
|
|
type: 'number',
|
|
description: 'The canonical issue number to duplicate others to',
|
|
demandOption: true,
|
|
})
|
|
.option('pr', {
|
|
type: 'string',
|
|
description:
|
|
'Optional Pull Request URL or ID to mention in the closing comment',
|
|
})
|
|
.option('owner', {
|
|
type: 'string',
|
|
default: 'google-gemini',
|
|
description: 'Repository owner',
|
|
})
|
|
.option('repo', {
|
|
type: 'string',
|
|
default: 'gemini-cli',
|
|
description: 'Repository name',
|
|
})
|
|
.option('dry-run', {
|
|
alias: 'd',
|
|
type: 'boolean',
|
|
default: false,
|
|
description: 'Run without making actual changes (read-only mode)',
|
|
})
|
|
.option('auto', {
|
|
type: 'boolean',
|
|
default: false,
|
|
description:
|
|
'Automatically close all duplicates without prompting (batch mode)',
|
|
})
|
|
.help()
|
|
.parse();
|
|
|
|
const octokit = new Octokit({
|
|
auth: process.env.GITHUB_TOKEN,
|
|
});
|
|
|
|
const { query, canonical, pr, owner, repo, dryRun, auto } = argv;
|
|
|
|
// Construct the full search query ensuring it targets the specific repo and open issues
|
|
const fullSearchQuery = `repo:${owner}/${repo} is:issue is:open ${query}`;
|
|
|
|
async function run() {
|
|
console.log(`Searching for issues matching: ${fullSearchQuery}`);
|
|
if (dryRun) {
|
|
console.log('--- DRY RUN MODE: No changes will be made ---');
|
|
}
|
|
|
|
try {
|
|
const issues = await octokit.paginate(
|
|
octokit.rest.search.issuesAndPullRequests,
|
|
{
|
|
q: fullSearchQuery,
|
|
},
|
|
);
|
|
|
|
console.log(`Found ${issues.length} issues.`);
|
|
|
|
for (const issue of issues) {
|
|
if (issue.number === canonical) {
|
|
console.log(`Skipping canonical issue #${issue.number}`);
|
|
continue;
|
|
}
|
|
|
|
console.log(
|
|
`Processing issue #${issue.number}: ${issue.title} (by @${issue.user?.login})`,
|
|
);
|
|
|
|
if (!auto && !dryRun) {
|
|
const response = await prompts({
|
|
type: 'confirm',
|
|
name: 'value',
|
|
message: `Close issue #${issue.number} "${issue.title}" created by @${issue.user?.login}?`,
|
|
initial: true,
|
|
});
|
|
|
|
if (!response.value) {
|
|
console.log(`Skipping issue #${issue.number}`);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
let commentBody = `Closing this issue as a duplicate of #${canonical}.`;
|
|
if (pr) {
|
|
commentBody += ` Please note that this issue should be resolved by PR ${pr}.`;
|
|
}
|
|
|
|
try {
|
|
if (!dryRun) {
|
|
// Add comment
|
|
await octokit.rest.issues.createComment({
|
|
owner,
|
|
repo,
|
|
issue_number: issue.number,
|
|
body: commentBody,
|
|
});
|
|
console.log(` Added comment.`);
|
|
|
|
// Close issue
|
|
await octokit.rest.issues.update({
|
|
owner,
|
|
repo,
|
|
issue_number: issue.number,
|
|
state: 'closed',
|
|
state_reason: 'duplicate',
|
|
});
|
|
console.log(` Closed issue.`);
|
|
} else {
|
|
console.log(` [DRY RUN] Would add comment: "${commentBody}"`);
|
|
console.log(` [DRY RUN] Would close issue #${issue.number}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(
|
|
` Failed to process issue #${issue.number}:`,
|
|
error.message,
|
|
);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error searching for issues:', error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
run().catch(console.error);
|