mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-02-01 14:44:29 +00:00
chore(automation): ensure status/need-triage is applied and never cleared automatically (#16657)
This commit is contained in:
138
.github/scripts/backfill-need-triage.cjs
vendored
Normal file
138
.github/scripts/backfill-need-triage.cjs
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
/* eslint-disable */
|
||||
/* global require, console, process */
|
||||
|
||||
/**
|
||||
* Script to backfill the 'status/need-triage' label to all open issues
|
||||
* that are NOT currently labeled with '🔒 maintainer only' or 'help wanted'.
|
||||
*/
|
||||
|
||||
const { execFileSync } = require('child_process');
|
||||
|
||||
const isDryRun = process.argv.includes('--dry-run');
|
||||
const REPO = 'google-gemini/gemini-cli';
|
||||
|
||||
/**
|
||||
* Executes a GitHub CLI command safely using an argument array to prevent command injection.
|
||||
* @param {string[]} args
|
||||
* @returns {string|null}
|
||||
*/
|
||||
function runGh(args) {
|
||||
try {
|
||||
// Using execFileSync with an array of arguments is safe as it doesn't use a shell.
|
||||
// We set a large maxBuffer (10MB) to handle repositories with many issues.
|
||||
return execFileSync('gh', args, {
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
} catch (error) {
|
||||
const stderr = error.stderr ? ` Stderr: ${error.stderr.trim()}` : '';
|
||||
console.error(
|
||||
`❌ Error running gh ${args.join(' ')}: ${error.message}${stderr}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🔐 GitHub CLI security check...');
|
||||
const authStatus = runGh(['auth', 'status']);
|
||||
if (authStatus === null) {
|
||||
console.error('❌ GitHub CLI (gh) is not installed or not authenticated.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (isDryRun) {
|
||||
console.log('🧪 DRY RUN MODE ENABLED - No changes will be made.\n');
|
||||
}
|
||||
|
||||
console.log(`🔍 Fetching and filtering open issues from ${REPO}...`);
|
||||
|
||||
// We use the /issues endpoint with pagination to bypass the 1000-result limit.
|
||||
// The jq filter ensures we exclude PRs, maintainer-only, help-wanted, and existing status/need-triage.
|
||||
const jqFilter =
|
||||
'.[] | select(.pull_request == null) | select([.labels[].name] as $l | (any($l[]; . == "🔒 maintainer only") | not) and (any($l[]; . == "help wanted") | not) and (any($l[]; . == "status/need-triage") | not)) | {number: .number, title: .title}';
|
||||
|
||||
const output = runGh([
|
||||
'api',
|
||||
`repos/${REPO}/issues?state=open&per_page=100`,
|
||||
'--paginate',
|
||||
'--jq',
|
||||
jqFilter,
|
||||
]);
|
||||
|
||||
if (output === null) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const issues = output
|
||||
.split('\n')
|
||||
.filter((line) => line.trim())
|
||||
.map((line) => {
|
||||
try {
|
||||
return JSON.parse(line);
|
||||
} catch (_e) {
|
||||
console.error(`⚠️ Failed to parse line: ${line}`);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
console.log(`✅ Found ${issues.length} issues matching criteria.`);
|
||||
|
||||
if (issues.length === 0) {
|
||||
console.log('✨ No issues need backfilling.');
|
||||
return;
|
||||
}
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
if (isDryRun) {
|
||||
for (const issue of issues) {
|
||||
console.log(
|
||||
`[DRY RUN] Would label issue #${issue.number}: ${issue.title}`,
|
||||
);
|
||||
}
|
||||
successCount = issues.length;
|
||||
} else {
|
||||
console.log(`🏷️ Applying labels to ${issues.length} issues...`);
|
||||
|
||||
for (const issue of issues) {
|
||||
const issueNumber = String(issue.number);
|
||||
console.log(`🏷️ Labeling issue #${issueNumber}: ${issue.title}`);
|
||||
|
||||
const result = runGh([
|
||||
'issue',
|
||||
'edit',
|
||||
issueNumber,
|
||||
'--add-label',
|
||||
'status/need-triage',
|
||||
'--repo',
|
||||
REPO,
|
||||
]);
|
||||
|
||||
if (result !== null) {
|
||||
successCount++;
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n📊 Summary:`);
|
||||
console.log(` - Success: ${successCount}`);
|
||||
console.log(` - Failed: ${failCount}`);
|
||||
|
||||
if (failCount > 0) {
|
||||
console.error(`\n❌ Backfill completed with ${failCount} errors.`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(`\n🎉 ${isDryRun ? 'Dry run' : 'Backfill'} complete!`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('❌ Unexpected error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
51
.github/scripts/pr-triage.sh
vendored
51
.github/scripts/pr-triage.sh
vendored
@@ -1,4 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# @license
|
||||
# Copyright 2026 Google LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Initialize a comma-separated string to hold PR numbers that need a comment
|
||||
@@ -10,7 +14,7 @@ ISSUE_LABELS_CACHE_FLAT=""
|
||||
|
||||
# Function to get area and priority labels from an issue (with caching)
|
||||
get_issue_labels() {
|
||||
local ISSUE_NUM=$1
|
||||
local ISSUE_NUM="${1}"
|
||||
if [[ -z "${ISSUE_NUM}" || "${ISSUE_NUM}" == "null" || "${ISSUE_NUM}" == "" ]]; then
|
||||
return
|
||||
fi
|
||||
@@ -18,10 +22,13 @@ get_issue_labels() {
|
||||
# Check cache
|
||||
case " ${ISSUE_LABELS_CACHE_FLAT} " in
|
||||
*" ${ISSUE_NUM}:"*)
|
||||
local suffix="${ISSUE_LABELS_CACHE_FLAT#* ${ISSUE_NUM}:}"
|
||||
local suffix="${ISSUE_LABELS_CACHE_FLAT#* " ${ISSUE_NUM}:"}"
|
||||
echo "${suffix%% *}"
|
||||
return
|
||||
;;
|
||||
*)
|
||||
# Cache miss, proceed to fetch
|
||||
;;
|
||||
esac
|
||||
|
||||
echo " 📥 Fetching area and priority labels from issue #${ISSUE_NUM}" >&2
|
||||
@@ -33,19 +40,19 @@ get_issue_labels() {
|
||||
fi
|
||||
|
||||
local labels
|
||||
labels=$(echo "${gh_output}" | grep -E "^(area|priority)/" | tr '\n' ',' | sed 's/,$//' || echo "")
|
||||
labels=$(echo "${gh_output}" | grep -E '^(area|priority)/' | tr '\n' ',' | sed 's/,$//' || echo "")
|
||||
|
||||
# Save to flat cache
|
||||
ISSUE_LABELS_CACHE_FLAT="${ISSUE_LABELS_CACHE_FLAT} ${ISSUE_NUM}:${labels}"
|
||||
echo "$labels"
|
||||
echo "${labels}"
|
||||
}
|
||||
|
||||
# Function to process a single PR with pre-fetched data
|
||||
process_pr_optimized() {
|
||||
local PR_NUMBER=$1
|
||||
local IS_DRAFT=$2
|
||||
local ISSUE_NUMBER=$3
|
||||
local CURRENT_LABELS=$4 # Comma-separated labels
|
||||
local PR_NUMBER="${1}"
|
||||
local IS_DRAFT="${2}"
|
||||
local ISSUE_NUMBER="${3}"
|
||||
local CURRENT_LABELS="${4}" # Comma-separated labels
|
||||
|
||||
echo "🔄 Processing PR #${PR_NUMBER}"
|
||||
|
||||
@@ -84,7 +91,7 @@ process_pr_optimized() {
|
||||
ISSUE_LABELS=$(get_issue_labels "${ISSUE_NUMBER}")
|
||||
|
||||
if [[ -n "${ISSUE_LABELS}" ]]; then
|
||||
local IFS_OLD=$IFS
|
||||
local IFS_OLD="${IFS}"
|
||||
IFS=','
|
||||
for label in ${ISSUE_LABELS}; do
|
||||
if [[ -n "${label}" ]] && [[ ",${CURRENT_LABELS}," != *",${label},"* ]]; then
|
||||
@@ -94,8 +101,8 @@ process_pr_optimized() {
|
||||
LABELS_TO_ADD="${LABELS_TO_ADD},${label}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
IFS=$IFS_OLD
|
||||
done
|
||||
IFS="${IFS_OLD}"
|
||||
fi
|
||||
|
||||
if [[ -z "${LABELS_TO_ADD}" && -z "${LABELS_TO_REMOVE}" ]]; then
|
||||
@@ -135,7 +142,7 @@ JQ_EXTRACT_FIELDS='{
|
||||
labels: [.labels[].name] | join(",")
|
||||
}'
|
||||
|
||||
JQ_TSV_FORMAT='"\((.number | tostring))\t\(.isDraft)\t\((.issue // "null") | tostring)\t\(.labels)"'
|
||||
JQ_TSV_FORMAT='"\((.number | tostring))\t\(.isDraft)\t\((.issue // \"null\") | tostring)\t\(.labels)"' # Corrected escaping for quotes within the string literal
|
||||
|
||||
if [[ -n "${PR_NUMBER:-}" ]]; then
|
||||
echo "🔄 Processing single PR #${PR_NUMBER}"
|
||||
@@ -144,9 +151,9 @@ if [[ -n "${PR_NUMBER:-}" ]]; then
|
||||
exit 1
|
||||
}
|
||||
|
||||
line=$(echo "$PR_DATA" | jq -r "$JQ_EXTRACT_FIELDS | $JQ_TSV_FORMAT")
|
||||
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "$line"
|
||||
process_pr_optimized "$pr_num" "$is_draft" "$issue_num" "$current_labels"
|
||||
line=$(echo "${PR_DATA}" | jq -r "${JQ_EXTRACT_FIELDS} | ${JQ_TSV_FORMAT}")
|
||||
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "${line}"
|
||||
process_pr_optimized "${pr_num}" "${is_draft}" "${issue_num}" "${current_labels}"
|
||||
else
|
||||
echo "📥 Getting all open pull requests..."
|
||||
PR_DATA_ALL=$(gh pr list --repo "${GITHUB_REPOSITORY}" --state open --limit 1000 --json number,closingIssuesReferences,isDraft,body,labels 2>/dev/null) || {
|
||||
@@ -157,11 +164,15 @@ else
|
||||
PR_COUNT=$(echo "${PR_DATA_ALL}" | jq '. | length')
|
||||
echo "📊 Found ${PR_COUNT} open PRs to process"
|
||||
|
||||
# Use a temporary file to avoid masking exit codes in process substitution
|
||||
tmp_file=$(mktemp)
|
||||
echo "${PR_DATA_ALL}" | jq -r ".[] | ${JQ_EXTRACT_FIELDS} | ${JQ_TSV_FORMAT}" > "${tmp_file}"
|
||||
while read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "$line"
|
||||
process_pr_optimized "$pr_num" "$is_draft" "$issue_num" "$current_labels"
|
||||
done < <(echo "${PR_DATA_ALL}" | jq -r ".[] | $JQ_EXTRACT_FIELDS | $JQ_TSV_FORMAT")
|
||||
[[ -z "${line}" ]] && continue
|
||||
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "${line}"
|
||||
process_pr_optimized "${pr_num}" "${is_draft}" "${issue_num}" "${current_labels}"
|
||||
done < "${tmp_file}"
|
||||
rm -f "${tmp_file}"
|
||||
fi
|
||||
|
||||
if [[ -z "${PRS_NEEDING_COMMENT}" ]]; then
|
||||
@@ -170,4 +181,4 @@ else
|
||||
echo "prs_needing_comment=[${PRS_NEEDING_COMMENT}]" >> "${GITHUB_OUTPUT}"
|
||||
fi
|
||||
|
||||
echo "✅ PR triage completed"
|
||||
echo "✅ PR triage completed"
|
||||
|
||||
@@ -95,7 +95,8 @@ jobs:
|
||||
id: 'generate_token'
|
||||
env:
|
||||
APP_ID: '${{ secrets.APP_ID }}'
|
||||
if: "${{ env.APP_ID != '' }}"
|
||||
if: |-
|
||||
${{ env.APP_ID != '' }}
|
||||
uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: '${{ secrets.APP_ID }}'
|
||||
@@ -305,22 +306,6 @@ jobs:
|
||||
});
|
||||
core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}`);
|
||||
|
||||
// Remove the 'status/need-triage' label
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
name: 'status/need-triage'
|
||||
});
|
||||
core.info(`Successfully removed 'status/need-triage' label.`);
|
||||
} catch (error) {
|
||||
// If the label doesn't exist, the API call will throw a 404. We can ignore this.
|
||||
if (error.status !== 404) {
|
||||
core.warning(`Failed to remove 'status/need-triage': ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
- name: 'Post Issue Analysis Failure Comment'
|
||||
if: |-
|
||||
${{ failure() && steps.gemini_issue_analysis.outcome == 'failure' }}
|
||||
|
||||
@@ -40,7 +40,8 @@ jobs:
|
||||
permission-issues: 'write'
|
||||
|
||||
- name: 'Get issue from event'
|
||||
if: "github.event_name == 'issues'"
|
||||
if: |-
|
||||
${{ github.event_name == 'issues' }}
|
||||
id: 'get_issue_from_event'
|
||||
env:
|
||||
ISSUE_EVENT: '${{ toJSON(github.event.issue) }}'
|
||||
@@ -51,7 +52,8 @@ jobs:
|
||||
echo "✅ Found issue #${{ github.event.issue.number }} from event to triage! 🎯"
|
||||
|
||||
- name: 'Find untriaged issues'
|
||||
if: "github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'"
|
||||
if: |-
|
||||
${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
|
||||
id: 'find_issues'
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token }}'
|
||||
@@ -161,7 +163,6 @@ jobs:
|
||||
9. For each issue please check if CLI version is present, this is usually in the output of the /about command and will look like 0.1.5
|
||||
- Anything more than 6 versions older than the most recent should add the status/need-retesting label
|
||||
10. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below.
|
||||
- After identifying appropriate labels to an issue, add "status/need-triage" label to labels_to_remove in the output.
|
||||
11. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation.
|
||||
12. If you are uncertain about a category, use the area/unknown, kind/question, or priority/unknown labels as appropriate. If you are extremely uncertain, apply the status/manual-triage label.
|
||||
|
||||
@@ -262,24 +263,6 @@ jobs:
|
||||
core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}${explanation}`);
|
||||
}
|
||||
|
||||
if (entry.labels_to_remove && entry.labels_to_remove.length > 0) {
|
||||
for (const label of entry.labels_to_remove) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
name: label
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status !== 404) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
core.info(`Successfully removed labels for #${issueNumber}: ${entry.labels_to_remove.join(', ')}`);
|
||||
}
|
||||
|
||||
if (entry.explanation) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
|
||||
46
.github/workflows/issue-opened-labeler.yml
vendored
Normal file
46
.github/workflows/issue-opened-labeler.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: '🏷️ Issue Opened Labeler'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- 'opened'
|
||||
|
||||
jobs:
|
||||
label-issue:
|
||||
runs-on: 'ubuntu-latest'
|
||||
if: |-
|
||||
${{ github.repository == 'google-gemini/gemini-cli' || github.repository == 'google-gemini/maintainers-gemini-cli' }}
|
||||
steps:
|
||||
- name: 'Generate GitHub App Token'
|
||||
id: 'generate_token'
|
||||
env:
|
||||
APP_ID: '${{ secrets.APP_ID }}'
|
||||
if: |-
|
||||
${{ env.APP_ID != '' }}
|
||||
uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: '${{ secrets.APP_ID }}'
|
||||
private-key: '${{ secrets.PRIVATE_KEY }}'
|
||||
|
||||
- name: 'Add need-triage label'
|
||||
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
|
||||
with:
|
||||
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
script: |-
|
||||
const { data: issue } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
|
||||
const hasLabel = issue.labels.some(l => l.name === 'status/need-triage');
|
||||
if (!hasLabel) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['status/need-triage']
|
||||
});
|
||||
} else {
|
||||
core.info('Issue already has status/need-triage label. Skipping.');
|
||||
}
|
||||
@@ -4,37 +4,39 @@
|
||||
# Example: ./scripts/batch_triage.sh google-gemini/maintainers-gemini-cli
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
REPO="${1:-google-gemini/gemini-cli}"
|
||||
WORKFLOW="gemini-automated-issue-triage.yml"
|
||||
|
||||
echo "🔍 Searching for open issues in '$REPO' that need triage (missing 'area/' label)..."
|
||||
echo "🔍 Searching for open issues in '${REPO}' that need triage (missing 'area/' label)..."
|
||||
|
||||
# Fetch open issues with number, title, and labels
|
||||
# We fetch up to 1000 issues.
|
||||
ISSUES_JSON=$(gh issue list --repo "$REPO" --state open --limit 1000 --json number,title,labels)
|
||||
ISSUES_JSON=$(gh issue list --repo "${REPO}" --state open --limit 1000 --json number,title,labels)
|
||||
|
||||
# Filter issues that DO NOT have a label starting with 'area/'
|
||||
TARGET_ISSUES=$(echo "$ISSUES_JSON" | jq '[.[] | select(.labels | map(.name) | any(startswith("area/")) | not)]')
|
||||
TARGET_ISSUES=$(echo "${ISSUES_JSON}" | jq '[.[] | select(.labels | map(.name) | any(startswith("area/")) | not)]')
|
||||
|
||||
COUNT=$(echo "$TARGET_ISSUES" | jq '. | length')
|
||||
# Avoid masking return value
|
||||
COUNT=$(jq '. | length' <<< "${TARGET_ISSUES}")
|
||||
|
||||
if [ "$COUNT" -eq 0 ]; then
|
||||
echo "✅ No issues found needing triage in '$REPO'."
|
||||
if [[ "${COUNT}" -eq 0 ]]; then
|
||||
echo "✅ No issues found needing triage in '${REPO}'."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "🚀 Found $COUNT issues to triage."
|
||||
echo "🚀 Found ${COUNT} issues to triage."
|
||||
|
||||
# Loop through and trigger workflow
|
||||
echo "$TARGET_ISSUES" | jq -r '.[] | "\(.number)|\(.title)"' | while IFS="|" read -r number title; do
|
||||
echo "▶️ Triggering triage for #$number: $title"
|
||||
echo "${TARGET_ISSUES}" | jq -r '.[] | "\(.number)|\(.title)"' | while IFS="|" read -r number title; do
|
||||
echo "▶️ Triggering triage for #${number}: ${title}"
|
||||
|
||||
# Trigger the workflow dispatch event
|
||||
gh workflow run "$WORKFLOW" --repo "$REPO" -f issue_number="$number"
|
||||
gh workflow run "${WORKFLOW}" --repo "${REPO}" -f issue_number="${number}"
|
||||
|
||||
# Sleep briefly to be nice to the API
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "🎉 All triage workflows triggered!"
|
||||
echo "🎉 All triage workflows triggered!"
|
||||
@@ -3,40 +3,42 @@
|
||||
# Usage: ./scripts/relabel_issues.sh <old-label> <new-label> [repository]
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
OLD_LABEL="$1"
|
||||
NEW_LABEL="$2"
|
||||
OLD_LABEL="${1}"
|
||||
NEW_LABEL="${2}"
|
||||
REPO="${3:-google-gemini/gemini-cli}"
|
||||
|
||||
if [ -z "$OLD_LABEL" ] || [ -z "$NEW_LABEL" ]; then
|
||||
if [[ -z "${OLD_LABEL}" ]] || [[ -z "${NEW_LABEL}" ]]; then
|
||||
echo "Usage: $0 <old-label> <new-label> [repository]"
|
||||
echo "Example: $0 'area/models' 'area/agent'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔍 Searching for open issues in '$REPO' with label '$OLD_LABEL'..."
|
||||
echo "🔍 Searching for open issues in '${REPO}' with label '${OLD_LABEL}'..."
|
||||
|
||||
# Fetch issues with the old label
|
||||
ISSUES=$(gh issue list --repo "$REPO" --label "$OLD_LABEL" --state open --limit 1000 --json number,title)
|
||||
ISSUES=$(gh issue list --repo "${REPO}" --label "${OLD_LABEL}" --state open --limit 1000 --json number,title)
|
||||
|
||||
COUNT=$(echo "$ISSUES" | jq '. | length')
|
||||
# Avoid masking return value
|
||||
COUNT=$(jq '. | length' <<< "${ISSUES}")
|
||||
|
||||
if [ "$COUNT" -eq 0 ]; then
|
||||
echo "✅ No issues found with label '$OLD_LABEL'."
|
||||
if [[ "${COUNT}" -eq 0 ]]; then
|
||||
echo "✅ No issues found with label '${OLD_LABEL}'."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "found $COUNT issues to relabel."
|
||||
echo "found ${COUNT} issues to relabel."
|
||||
|
||||
# Iterate and update
|
||||
echo "$ISSUES" | jq -r '.[] | "\(.number) \(.title)"' | while read -r number title; do
|
||||
echo "🔄 Processing #$number: $title"
|
||||
echo " - Removing: $OLD_LABEL"
|
||||
echo " + Adding: $NEW_LABEL"
|
||||
echo "${ISSUES}" | jq -r '.[] | "\(.number) \(.title)"' | while read -r number title; do
|
||||
echo "🔄 Processing #${number}: ${title}"
|
||||
echo " - Removing: ${OLD_LABEL}"
|
||||
echo " + Adding: ${NEW_LABEL}"
|
||||
|
||||
gh issue edit "$number" --repo "$REPO" --add-label "$NEW_LABEL" --remove-label "$OLD_LABEL"
|
||||
gh issue edit "${number}" --repo "${REPO}" --add-label "${NEW_LABEL}" --remove-label "${OLD_LABEL}"
|
||||
|
||||
echo " ✅ Done."
|
||||
done
|
||||
|
||||
echo "🎉 All issues relabeled!"
|
||||
echo "🎉 All issues relabeled!"
|
||||
@@ -30,9 +30,10 @@
|
||||
set -e -E
|
||||
|
||||
# Load environment variables from .env if it exists
|
||||
if [ -f ".env" ]; then
|
||||
if [[ -f ".env" ]]; then
|
||||
echo "Loading environment variables from .env file..."
|
||||
set -a # Automatically export all variables
|
||||
# shellcheck source=/dev/null
|
||||
source .env
|
||||
set +a
|
||||
fi
|
||||
@@ -49,32 +50,32 @@ STREAM_MODE=false
|
||||
# Parse command line arguments
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case $1 in
|
||||
--payload) PAYLOAD_FILE="$2"; shift ;;
|
||||
--model) MODEL_ID="$2"; shift ;;
|
||||
--payload) PAYLOAD_FILE="${2}"; shift ;;
|
||||
--model) MODEL_ID="${2}"; shift ;;
|
||||
--stream) STREAM_MODE=true ;;
|
||||
*) echo "Unknown parameter passed: $1"; usage ;;
|
||||
*) echo "Unknown parameter passed: ${1}"; usage ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Validate inputs
|
||||
if [ -z "$PAYLOAD_FILE" ] || [ -z "$MODEL_ID" ]; then
|
||||
if [[ -z "${PAYLOAD_FILE}" ]] || [[ -z "${MODEL_ID}" ]]; then
|
||||
echo "Error: Missing required arguments."
|
||||
usage
|
||||
fi
|
||||
|
||||
if [ -z "$GEMINI_API_KEY" ]; then
|
||||
if [[ -z "${GEMINI_API_KEY}" ]]; then
|
||||
echo "Error: GEMINI_API_KEY environment variable is not set."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$PAYLOAD_FILE" ]; then
|
||||
echo "Error: Payload file '$PAYLOAD_FILE' does not exist."
|
||||
if [[ ! -f "${PAYLOAD_FILE}" ]]; then
|
||||
echo "Error: Payload file '${PAYLOAD_FILE}' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# API Endpoint definition
|
||||
if [ "$STREAM_MODE" = true ]; then
|
||||
if [[ "${STREAM_MODE}" = true ]]; then
|
||||
GENERATE_CONTENT_API="streamGenerateContent"
|
||||
echo "Mode: Streaming"
|
||||
else
|
||||
@@ -82,16 +83,18 @@ else
|
||||
echo "Mode: Non-streaming (Default)"
|
||||
fi
|
||||
|
||||
echo "Sending request to model: $MODEL_ID"
|
||||
echo "Using payload from: $PAYLOAD_FILE"
|
||||
echo "Sending request to model: ${MODEL_ID}"
|
||||
echo "Using payload from: ${PAYLOAD_FILE}"
|
||||
echo "----------------------------------------"
|
||||
|
||||
# Make the cURL request. If non-streaming, pipe through jq for readability if available.
|
||||
if [ "$STREAM_MODE" = false ] && command -v jq &> /dev/null; then
|
||||
curl -s -X POST \
|
||||
if [[ "${STREAM_MODE}" = false ]] && command -v jq &> /dev/null; then
|
||||
# Invoke curl separately to avoid masking its return value
|
||||
output=$(curl -s -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/${MODEL_ID}:${GENERATE_CONTENT_API}?key=${GEMINI_API_KEY}" \
|
||||
-d "@${PAYLOAD_FILE}" | jq .
|
||||
-d "@${PAYLOAD_FILE}")
|
||||
echo "${output}" | jq .
|
||||
else
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
|
||||
Reference in New Issue
Block a user