diff --git a/.claude/commands/dedupe.md b/.claude/commands/dedupe.md new file mode 100644 index 0000000000..d58ce248cb --- /dev/null +++ b/.claude/commands/dedupe.md @@ -0,0 +1,38 @@ +--- +allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh api:*), Bash(gh issue comment:*) +description: Find duplicate GitHub issues +--- + +Find up to 3 likely duplicate issues for a given GitHub issue. + +To do this, follow these steps precisely: + +1. Use an agent to check if the Github issue (a) is closed, (b) does not need to be deduped (eg. because it is broad product feedback without a specific solution, or positive feedback), or (c) already has a duplicates comment that you made earlier. If so, do not proceed. +2. Use an agent to view a Github issue, and ask the agent to return a summary of the issue +3. Then, launch 5 parallel agents to search Github for duplicates of this issue, using diverse keywords and search approaches, using the summary from #1 +4. Next, feed the results from #1 and #2 into another agent, so that it can filter out false positives, that are likely not actually duplicates of the original issue. If there are no duplicates remaining, do not proceed. +5. Finally, comment back on the issue with a list of up to three duplicate issues (or zero, if there are no likely duplicates) + +Notes (be sure to tell this to your agents, too): + +- Use `gh` to interact with Github, rather than web fetch +- Do not use other tools, beyond `gh` (eg. don't use other MCP servers, file edit, etc.) +- Make a todo list first +- For your comment, follow the following format precisely (assuming for this example that you found 3 suspected duplicates): + +--- + +Found 3 possible duplicate issues: + +1. +2. +3. + +This issue will be automatically closed as a duplicate in 3 days. + +- If your issue is a duplicate, please close it and 👍 the existing issue instead +- To prevent auto-closure, add a comment or 👎 this comment + +> 🤖 Generated with Claude Code + +--- diff --git a/.claude/prompts/team-assignment.md b/.claude/prompts/team-assignment.md new file mode 100644 index 0000000000..64259f9988 --- /dev/null +++ b/.claude/prompts/team-assignment.md @@ -0,0 +1,135 @@ +# Team Assignment Guide + +## Quick Reference by Name + +- **@arvinxx**: Last resort only, mention for priority:high issues, tool calling , mcp +- **@canisminor1990**: Design, UI components, editor +- **@tjx666**: Image/video generation, vision, cloud, documentation, TTS +- **@ONLY-yours**: Performance, streaming, settings, general bugs, web platform, marketplace +- **@RiverTwilight**: Knowledge base, files (KB-related), group chat +- **@nekomeowww**: Memory, backend, deployment, DevOps +- **@sudongyuer**: Mobile app (React Native) +- **@sxjeru**: Model providers and configuration +- **@cy948**: Auth Modules +- **@rdmclin2**: Team workspace + +Quick reference for assigning issues based on labels. + +## Label to Team Member Mapping + +### Provider Labels (provider:\*) + +| Label | Owner | Notes | +| ---------------- | ------- | -------------------------------------------- | +| All `provider:*` | @sxjeru | Model configuration and provider integration | + +### Platform Labels (platform:\*) + +| Label | Owner | Notes | +| ------------------ | ----------- | -------------------------------------- | +| `platform:mobile` | @sudongyuer | React Native mobile app | +| `platform:desktop` | @ONLY-yours | Electron desktop client (general) | +| `platform:web` | @ONLY-yours | Web platform (unless specific feature) | + +### Feature Labels (feature:\*) + +| Label | Owner | Notes | +| ------------------------ | --------------- | ----------------------------------------------------------------------- | +| `feature:image` | @tjx666 | AI image generation | +| `feature:dalle` | @tjx666 | DALL-E related | +| `feature:vision` | @tjx666 | Vision/multimodal generation | +| `feature:knowledge-base` | @RiverTwilight | Knowledge base and RAG | +| `feature:files` | @RiverTwilight | File upload/management (when KB-related)
@ONLY-yours (general files) | +| `feature:editor` | @canisminor1990 | Lobe Editor | +| `feature:auth` | @cy948 | Authentication/authorization | +| `feature:api` | @nekomeowww | Backend API | +| `feature:streaming` | @arvinxx | Streaming response | +| `feature:settings` | @ONLY-yours | Settings and configuration | +| `feature:agent` | @ONLY-yours | Agent/Assistant | +| `feature:topic` | @ONLY-yours | Topic/Conversation management | +| `feature:thread` | @arvinxx | Thread/Subtopic | +| `feature:marketplace` | @ONLY-yours | Agent marketplace | +| `feature:tool` | @arvinxx | Tool calling | +| `feature:mcp` | @arvinxx | MCP integration | +| `feature:search` | @ONLY-yours | Search functionality | +| `feature:tts` | @tjx666 | Text-to-speech | +| `feature:export` | @ONLY-yours | Export functionality | +| `feature:group-chat` | @RiverTwilight | Group chat functionality | +| `feature:memory` | @nekomeowww | Memory feature | +| `feature:team-workspace` | @rdmclin2 | Team workspace application | + +### Deployment Labels (deployment:\*) + +| Label | Owner | Notes | +| ------------------ | ----------- | -------------------------- | +| All `deployment:*` | @nekomeowww | Server/client/pglite modes | + +### Hosting Labels (hosting:\*) + +| Label | Owner | Notes | +| ------------------- | ----------- | ---------------------- | +| `hosting:cloud` | @tjx666 | Official LobeHub Cloud | +| `hosting:self-host` | @nekomeowww | Self-hosting issues | +| `hosting:vercel` | @nekomeowww | Vercel deployment | +| `hosting:zeabur` | @nekomeowww | Zeabur deployment | +| `hosting:railway` | @nekomeowww | Railway deployment | + +### Issue Type Labels + +| Label | Owner | Notes | +| ------------------ | -------------------- | ---------------------------- | +| 💄 Design | @canisminor1990 | Design and styling | +| 📝 Documentation | @tjx666 | Documentation | +| ⚡️ Performance | @ONLY-yours | Performance optimization | +| 🐛 Bug | (depends on feature) | Assign based on other labels | +| 🌠 Feature Request | (depends on feature) | Assign based on other labels | + +## Assignment Rules + +### Priority Order (apply in order) + +1. **Specific feature owner** - e.g., `feature:knowledge-base` → @RiverTwilight +2. **Platform owner** - e.g., `platform:mobile` → @sudongyuer +3. **Provider owner** - e.g., `provider:*` → @sxjeru +4. **Component owner** - e.g., 💄 Design → @canisminor1990 +5. **Infrastructure owner** - e.g., `deployment:*` → @nekomeowww +6. **General maintainer** - @ONLY-yours for general bugs/issues +7. **Last resort** - @arvinxx (only if no clear owner) + +### Special Cases + +**Multiple labels with different owners:** + +- Mention the **most specific** feature owner first +- Mention secondary owners if their input is valuable +- Example: `feature:knowledge-base` + `deployment:server` → @RiverTwilight (primary), @nekomeowww (secondary) + +**Priority:high issues:** + +- Mention feature owner + @arvinxx +- Example: `priority:high` + `feature:image` → @tjx666 @arvinxx + +**No clear owner:** + +- Assign to @ONLY-yours for general issues +- Only mention @arvinxx if critical and truly unclear + +## Comment Templates + +**Single owner:** + +``` +@username - This is a [feature/component] issue. Please take a look. +``` + +**Multiple owners:** + +``` +@primary @secondary - This involves [features]. Please coordinate. +``` + +**High priority:** + +``` +@owner @arvinxx - High priority [feature] issue. +``` diff --git a/.github/scripts/auto-close-duplicates.ts b/.github/scripts/auto-close-duplicates.ts new file mode 100644 index 0000000000..b4fbebeb3d --- /dev/null +++ b/.github/scripts/auto-close-duplicates.ts @@ -0,0 +1,260 @@ +#!/usr/bin/env bun + +declare global { + // @ts-ignore + // eslint-disable-next-line no-var + var process: { + env: Record; + }; +} + +interface GitHubIssue { + created_at: string; + number: number; + title: string; + user: { id: number }; +} + +interface GitHubComment { + body: string; + created_at: string; + id: number; + user: { id: number; type: string }; +} + +interface GitHubReaction { + content: string; + user: { id: number }; +} + +async function githubRequest( + endpoint: string, + token: string, + method: string = 'GET', + body?: any, +): Promise { + const response = await fetch(`https://api.github.com${endpoint}`, { + headers: { + 'Accept': 'application/vnd.github.v3+json', + 'Authorization': `Bearer ${token}`, + 'User-Agent': 'auto-close-duplicates-script', + ...(body && { 'Content-Type': 'application/json' }), + }, + method, + ...(body && { body: JSON.stringify(body) }), + }); + + if (!response.ok) { + throw new Error(`GitHub API request failed: ${response.status} ${response.statusText}`); + } + + return response.json(); +} + +function extractDuplicateIssueNumber(commentBody: string): number | null { + // Try to match #123 format first + let match = commentBody.match(/#(\d+)/); + if (match) { + return parseInt(match[1], 10); + } + + // Try to match GitHub issue URL format: https://github.com/owner/repo/issues/123 + match = commentBody.match(/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/); + if (match) { + return parseInt(match[1], 10); + } + + return null; +} + +async function closeIssueAsDuplicate( + owner: string, + repo: string, + issueNumber: number, + duplicateOfNumber: number, + token: string, +): Promise { + await githubRequest(`/repos/${owner}/${repo}/issues/${issueNumber}`, token, 'PATCH', { + labels: ['duplicate'], + state: 'closed', + state_reason: 'duplicate', + }); + + await githubRequest(`/repos/${owner}/${repo}/issues/${issueNumber}/comments`, token, 'POST', { + body: `This issue has been automatically closed as a duplicate of #${duplicateOfNumber}. + +If this is incorrect, please re-open this issue or create a new one. + +🤖 Generated with [Claude Code](https://claude.ai/code)`, + }); +} + +async function autoCloseDuplicates(): Promise { + console.log('[DEBUG] Starting auto-close duplicates script'); + + const token = process.env.GITHUB_TOKEN; + if (!token) { + throw new Error('GITHUB_TOKEN environment variable is required'); + } + console.log('[DEBUG] GitHub token found'); + + const owner = process.env.GITHUB_REPOSITORY_OWNER || 'lobehub'; + const repo = process.env.GITHUB_REPOSITORY_NAME || 'lobe-chat'; + console.log(`[DEBUG] Repository: ${owner}/${repo}`); + + const threeDaysAgo = new Date(); + threeDaysAgo.setDate(threeDaysAgo.getDate() - 3); + console.log(`[DEBUG] Checking for duplicate comments older than: ${threeDaysAgo.toISOString()}`); + + console.log('[DEBUG] Fetching open issues created more than 3 days ago...'); + const allIssues: GitHubIssue[] = []; + let page = 1; + const perPage = 100; + + // eslint-disable-next-line no-constant-condition + while (true) { + const pageIssues: GitHubIssue[] = await githubRequest( + `/repos/${owner}/${repo}/issues?state=open&per_page=${perPage}&page=${page}`, + token, + ); + + if (pageIssues.length === 0) break; + + // Filter for issues created more than 3 days ago + const oldEnoughIssues = pageIssues.filter( + (issue) => new Date(issue.created_at) <= threeDaysAgo, + ); + + allIssues.push(...oldEnoughIssues); + page++; + + // Safety limit to avoid infinite loops + if (page > 20) break; + } + + const issues = allIssues; + console.log(`[DEBUG] Found ${issues.length} open issues`); + + let processedCount = 0; + let candidateCount = 0; + + for (const issue of issues) { + processedCount++; + console.log( + `[DEBUG] Processing issue #${issue.number} (${processedCount}/${issues.length}): ${issue.title}`, + ); + + console.log(`[DEBUG] Fetching comments for issue #${issue.number}...`); + const comments: GitHubComment[] = await githubRequest( + `/repos/${owner}/${repo}/issues/${issue.number}/comments`, + token, + ); + console.log(`[DEBUG] Issue #${issue.number} has ${comments.length} comments`); + + const dupeComments = comments.filter( + (comment) => + comment.body.includes('Found') && + comment.body.includes('possible duplicate') && + comment.user.type === 'Bot', + ); + console.log( + `[DEBUG] Issue #${issue.number} has ${dupeComments.length} duplicate detection comments`, + ); + + if (dupeComments.length === 0) { + console.log(`[DEBUG] Issue #${issue.number} - no duplicate comments found, skipping`); + continue; + } + + const lastDupeComment = dupeComments.at(-1); + // @ts-ignore + const dupeCommentDate = new Date(lastDupeComment.created_at); + console.log( + `[DEBUG] Issue #${ + issue.number + } - most recent duplicate comment from: ${dupeCommentDate.toISOString()}`, + ); + + if (dupeCommentDate > threeDaysAgo) { + console.log(`[DEBUG] Issue #${issue.number} - duplicate comment is too recent, skipping`); + continue; + } + console.log( + `[DEBUG] Issue #${issue.number} - duplicate comment is old enough (${Math.floor( + (Date.now() - dupeCommentDate.getTime()) / (1000 * 60 * 60 * 24), + )} days)`, + ); + + const commentsAfterDupe = comments.filter( + (comment) => new Date(comment.created_at) > dupeCommentDate, + ); + console.log( + `[DEBUG] Issue #${issue.number} - ${commentsAfterDupe.length} comments after duplicate detection`, + ); + + if (commentsAfterDupe.length > 0) { + console.log( + `[DEBUG] Issue #${issue.number} - has activity after duplicate comment, skipping`, + ); + continue; + } + + console.log(`[DEBUG] Issue #${issue.number} - checking reactions on duplicate comment...`); + const reactions: GitHubReaction[] = await githubRequest( + // @ts-ignore + `/repos/${owner}/${repo}/issues/comments/${lastDupeComment.id}/reactions`, + token, + ); + console.log( + `[DEBUG] Issue #${issue.number} - duplicate comment has ${reactions.length} reactions`, + ); + + const authorThumbsDown = reactions.some( + (reaction) => reaction.user.id === issue.user.id && reaction.content === '-1', + ); + console.log( + `[DEBUG] Issue #${issue.number} - author thumbs down reaction: ${authorThumbsDown}`, + ); + + if (authorThumbsDown) { + console.log( + `[DEBUG] Issue #${issue.number} - author disagreed with duplicate detection, skipping`, + ); + continue; + } + + // @ts-ignore + const duplicateIssueNumber = extractDuplicateIssueNumber(lastDupeComment.body); + if (!duplicateIssueNumber) { + console.log( + `[DEBUG] Issue #${issue.number} - could not extract duplicate issue number from comment, skipping`, + ); + continue; + } + + candidateCount++; + const issueUrl = `https://github.com/${owner}/${repo}/issues/${issue.number}`; + + try { + console.log( + `[INFO] Auto-closing issue #${issue.number} as duplicate of #${duplicateIssueNumber}: ${issueUrl}`, + ); + await closeIssueAsDuplicate(owner, repo, issue.number, duplicateIssueNumber, token); + console.log( + `[SUCCESS] Successfully closed issue #${issue.number} as duplicate of #${duplicateIssueNumber}`, + ); + } catch (error) { + console.error(`[ERROR] Failed to close issue #${issue.number} as duplicate: ${error}`); + } + } + + console.log( + `[DEBUG] Script completed. Processed ${processedCount} issues, found ${candidateCount} candidates for auto-close`, + ); +} + +// eslint-disable-next-line unicorn/prefer-top-level-await +autoCloseDuplicates().catch(console.error); + +// Make it a module +export {}; diff --git a/.github/workflows/claude-dedupe-issues.yml b/.github/workflows/claude-dedupe-issues.yml new file mode 100644 index 0000000000..b2d51c22a8 --- /dev/null +++ b/.github/workflows/claude-dedupe-issues.yml @@ -0,0 +1,33 @@ +name: Claude Issue Dedupe +description: Automatically dedupe GitHub issues using Claude Code +on: + issues: + types: [opened] + workflow_dispatch: + inputs: + issue_number: + description: 'Issue number to process for duplicate detection' + required: true + type: string + +jobs: + claude-dedupe-issues: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + issues: write + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 1 + + - name: Run Claude Code slash command + uses: anthropics/claude-code-base-action@beta + with: + prompt: '/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}' + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + claude_env: | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/claude-issue-triage.yml b/.github/workflows/claude-issue-triage.yml new file mode 100644 index 0000000000..da4c7b9fed --- /dev/null +++ b/.github/workflows/claude-issue-triage.yml @@ -0,0 +1,199 @@ +name: Claude Issue Triage +description: Automatically triage GitHub issues using Claude Code +on: + issues: + types: [opened, labeled] + +jobs: + triage-issue: + runs-on: ubuntu-latest + timeout-minutes: 10 + # Only run on issue opened, or when "trigger:triage" label is added + if: github.event.action == 'opened' || (github.event.action == 'labeled' && github.event.label.name == 'trigger:triage') + permissions: + contents: read + issues: write + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Copy team assignment guide + run: | + mkdir -p /tmp/claude-prompts + cp .claude/prompts/team-assignment.md /tmp/claude-prompts/ + + - name: Create triage prompt + run: | + mkdir -p /tmp/claude-prompts + cat > /tmp/claude-prompts/triage-prompt.md << 'EOF' + You're an issue triage assistant for GitHub issues. Your task is to analyze issues, apply appropriate labels, and mention the responsible team member. + + REPOSITORY: ${{ github.repository }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + + ## WORKFLOW + + ### Step 1: Get Available Labels + ```bash + gh label list --json name,description --limit 300 + ``` + + ### Step 2: Get Issue Details + ```bash + gh issue view ${{ github.event.issue.number }} --json number,title,body,labels,comments + ``` + + ### Step 3: Read Team Assignment Guide + ```bash + cat /tmp/claude-prompts/team-assignment.md + ``` + + ### Step 4: Analyze and Select Labels + + Extract information from the issue template: + - 📦 Platform field → platform:web/desktop/mobile + - 💻 Operating System → os:windows/macos/linux/ios + - 🌐 Browser → device:pc/mobile + - 📦 Deployment mode → deployment:server/client/pglite + - Platform (hosting) → hosting:cloud/self-host/vercel/zeabur/railway + + **LABEL CATEGORIES:** + + **a) Issue Type (REQUIRED - select ONE):** + - 🐛 Bug, 🌠 Feature Request, 💄 Design, 📝 Documentation, ⚡️ Performance + + **b) Priority (select ONE if applicable):** + - priority:high - Critical, maintainer mentions "urgent"/"serious"/"critical" + - priority:medium - Important, affects multiple users + - priority:low - Nice to have, minor issues + + **c) Platform (select ALL applicable):** + - platform:web, platform:desktop, platform:mobile + + **d) Device (for platform:web, select ONE):** + - device:pc, device:mobile + + **e) Operating System (select ALL applicable):** + - os:windows, os:macos, os:linux, os:ios + + **f) Hosting Platform (select ONE):** + - hosting:cloud, hosting:self-host, hosting:vercel, hosting:zeabur, hosting:railway + + **g) Deployment Mode (select ONE if mentioned):** + - deployment:server, deployment:client, deployment:pglite + + **h) Model Provider (select ALL applicable):** + - provider:openai, provider:gemini, provider:claude, provider:deepseek + - provider:google, provider:ollama, provider:azure, provider:bedrock, provider:vertex + + **i) Feature/Component (select ALL applicable):** + - feature:settings, feature:agent, feature:topic, feature:marketplace + - feature:streaming, feature:tool, feature:sync, feature:export + - feature:search, feature:auth, feature:files, feature:knowledge-base + - feature:tts, feature:vision, feature:mcp, feature:editor, feature:thread + - feature:image, feature:api, feature:dalle, feature:plugin, feature:markdown + - feature:group-chat, feature:memory, feature:team-workspace + + **j) Workflow/Status:** + - Duplicate - Only if duplicate of OPEN issue + - needs-reproduction, good-first-issue, 🤔 Need Reproduce + + **IMPORTANT RULES:** + - Read issue template fields carefully + - Check maintainer comments for priority/status + - Use ALL applicable labels from different categories + - Always use prefixes (feature:, provider:, os:, platform:, etc.) + + ### Step 5: Apply Labels + + Add labels: + ```bash + gh issue edit ${{ github.event.issue.number }} --add-label "label1,label2,label3" + ``` + + Remove "unconfirm" if adding other labels: + ```bash + gh issue edit ${{ github.event.issue.number }} --remove-label "unconfirm" + ``` + + **Execute the commands now.** + + ### Step 6: Determine Team Member + + Based on the team assignment guide and applied labels, determine who to mention. + + **Priority Order:** + 1. Specific feature owner (feature:knowledge-base → @RiverTwilight) + 2. Platform owner (platform:mobile → @sudongyuer) + 3. Provider owner (provider:* → @sxjeru) + 4. Component owner (💄 Design → @canisminor1990) + 5. Infrastructure owner (deployment:* → @nekomeowww) + 6. General maintainer (@ONLY-yours) + 7. Last resort (@arvinxx) + + **Special Cases:** + - Multiple owners: Mention primary + secondary + - priority:high: Mention owner + @arvinxx + + ### Step 7: Post Comment + + Format the comment (1-2 sentences): + + **Single owner:** + ``` + @username - This is a [feature/component] issue. Please take a look. + ``` + + **Multiple owners:** + ``` + @user1 @user2 - This involves [features]. Please coordinate. + ``` + + **High priority:** + ``` + @owner @arvinxx - High priority [feature] issue. + ``` + + Post the comment: + ```bash + gh issue comment ${{ github.event.issue.number }} --body "@username - [message]" + ``` + + **Execute the command now.** + + ### Step 8: Output Summary + + Log your reasoning (2-4 sentences): + - Labels applied and why + - Team member(s) mentioned and reason + - Key factors from issue template/comments + + ## GUIDELINES + + - Be thorough in analysis + - Use only labels from the provided list + - Extract info from issue template fields + - ALWAYS post a mention comment (unless no clear owner) + - Keep comments professional and brief + - Output reasoning to logs + + **Start the triage process now.** + EOF + + - name: Run Claude Code for Issue Triage + uses: anthropics/claude-code-base-action@beta + with: + prompt_file: /tmp/claude-prompts/triage-prompt.md + allowed_tools: "Bash(gh *),Read" + timeout_minutes: "5" + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + claude_env: | + GH_TOKEN: ${{ secrets.GH_TOKEN }} + + - name: Remove trigger label + if: github.event.action == 'labeled' && github.event.label.name == 'trigger:triage' + run: | + gh issue edit ${{ github.event.issue.number }} --remove-label "trigger:triage" + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/desktop-pr-build.yml b/.github/workflows/desktop-pr-build.yml index cf83b846a3..a04a52b01d 100644 --- a/.github/workflows/desktop-pr-build.yml +++ b/.github/workflows/desktop-pr-build.yml @@ -18,8 +18,8 @@ env: jobs: test: name: Code quality check - # 添加 PR label 触发条件,只有添加了 Build Desktop 标签的 PR 才会触发构建 - if: contains(github.event.pull_request.labels.*.name, 'Build Desktop') + # 添加 PR label 触发条件,只有添加了 trigger:build-desktop 标签的 PR 才会触发构建 + if: contains(github.event.pull_request.labels.*.name, 'trigger:build-desktop') runs-on: ubuntu-latest # 只在 ubuntu 上运行一次检查 steps: - name: Checkout base @@ -51,7 +51,7 @@ jobs: version: name: Determine version # 与 test job 相同的触发条件 - if: contains(github.event.pull_request.labels.*.name, 'Build Desktop') + if: contains(github.event.pull_request.labels.*.name, 'trigger:build-desktop') runs-on: ubuntu-latest outputs: # 输出版本信息,供后续 job 使用 diff --git a/.github/workflows/docker-database.yml b/.github/workflows/docker-database.yml index a27ab29e04..a065ea741f 100644 --- a/.github/workflows/docker-database.yml +++ b/.github/workflows/docker-database.yml @@ -22,7 +22,7 @@ jobs: # 添加 PR label 触发条件 if: | (github.event_name == 'pull_request' && - contains(github.event.pull_request.labels.*.name, 'Build Docker')) || + contains(github.event.pull_request.labels.*.name, 'trigger:build-docker')) || github.event_name != 'pull_request' strategy: diff --git a/.github/workflows/docker-pglite.yml b/.github/workflows/docker-pglite.yml index 22084e6e0f..fc1900676e 100644 --- a/.github/workflows/docker-pglite.yml +++ b/.github/workflows/docker-pglite.yml @@ -22,7 +22,7 @@ jobs: # 添加 PR label 触发条件 if: | (github.event_name == 'pull_request' && - contains(github.event.pull_request.labels.*.name, 'Build Docker')) || + contains(github.event.pull_request.labels.*.name, 'trigger:build-docker')) || github.event_name != 'pull_request' strategy: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 32468b4e35..ff211bf3bc 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -22,7 +22,7 @@ jobs: # 添加 PR label 触发条件 if: | (github.event_name == 'pull_request' && - contains(github.event.pull_request.labels.*.name, 'Build Docker')) || + contains(github.event.pull_request.labels.*.name, 'trigger:build-docker')) || github.event_name != 'pull_request' strategy: diff --git a/.github/workflows/issue-auto-close-duplicates.yml b/.github/workflows/issue-auto-close-duplicates.yml new file mode 100644 index 0000000000..9523e5a527 --- /dev/null +++ b/.github/workflows/issue-auto-close-duplicates.yml @@ -0,0 +1,30 @@ +name: Auto-close duplicate issues +description: Auto-closes issues that are duplicates of existing issues +on: + schedule: + - cron: "0 2 * * *" + workflow_dispatch: + +jobs: + auto-close-duplicates: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + issues: write + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Auto-close duplicate issues + run: bun run .github/scripts/auto-close-duplicates.ts + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }} + GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }} diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index 06bf6fd693..5d3a6a989f 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -15,7 +15,7 @@ permissions: read-all jobs: test: name: Code quality check - # 添加 PR label 触发条件,只有添加了 Build Desktop 标签的 PR 才会触发构建 + # 添加 PR label 触发条件,只有添加了 trigger:build-desktop 标签的 PR 才会触发构建 runs-on: ubuntu-latest # 只在 ubuntu 上运行一次检查 steps: - name: Checkout base