# This workflow automatically notifies codeowners when an issue is labeled with component labels. # It reads the CODEOWNERS file to find the maintainers for the labeled components # and posts a comment mentioning them to ensure they're aware of the issue. name: Notify Issue Codeowners on: issues: types: [labeled] permissions: issues: write contents: read jobs: notify-codeowners: name: Run if: ${{ startsWith(github.event.label.name, format('component{0} ', ':')) }} runs-on: ubuntu-latest steps: - name: Notify codeowners for component issues uses: actions/github-script@v7.0.1 with: script: | const owner = context.repo.owner; const repo = context.repo.repo; const issue_number = context.payload.issue.number; const labelName = context.payload.label.name; console.log(`Processing issue #${issue_number} with label: ${labelName}`); // Extract component name from label const componentName = labelName.replace('component: ', ''); console.log(`Component: ${componentName}`); try { // Fetch CODEOWNERS file from root const { data: codeownersFile } = await github.rest.repos.getContent({ owner, repo, path: 'CODEOWNERS' }); const codeownersContent = Buffer.from(codeownersFile.content, 'base64').toString('utf8'); // Parse CODEOWNERS file to extract component mappings const codeownersLines = codeownersContent.split('\n') .map(line => line.trim()) .filter(line => line && !line.startsWith('#')); let componentOwners = null; for (const line of codeownersLines) { const parts = line.split(/\s+/); if (parts.length < 2) continue; const pattern = parts[0]; const owners = parts.slice(1); // Look for component patterns: esphome/components/{component}/* const componentMatch = pattern.match(/^esphome\/components\/([^\/]+)\/\*$/); if (componentMatch && componentMatch[1] === componentName) { componentOwners = owners; break; } } if (!componentOwners) { console.log(`No codeowners found for component: ${componentName}`); return; } console.log(`Found codeowners for '${componentName}': ${componentOwners.join(', ')}`); // Separate users and teams const userOwners = []; const teamOwners = []; for (const owner of componentOwners) { const cleanOwner = owner.startsWith('@') ? owner.slice(1) : owner; if (cleanOwner.includes('/')) { // Team mention (org/team-name) teamOwners.push(`@${cleanOwner}`); } else { // Individual user userOwners.push(`@${cleanOwner}`); } } // Remove issue author from mentions to avoid self-notification const issueAuthor = context.payload.issue.user.login; const filteredUserOwners = userOwners.filter(mention => mention !== `@${issueAuthor}` ); const allMentions = [...filteredUserOwners, ...teamOwners]; if (allMentions.length === 0) { console.log('No codeowners to notify (issue author is the only codeowner)'); return; } // Create comment body const mentionString = allMentions.join(', '); const commentBody = `👋 Hey ${mentionString}!\n\nThis issue has been labeled with \`component: ${componentName}\` and you've been identified as a codeowner of this component. Please take a look when you have a chance!\n\nThanks for maintaining this component! 🙏`; // Post comment await github.rest.issues.createComment({ owner, repo, issue_number: issue_number, body: commentBody }); console.log(`Successfully notified codeowners: ${mentionString}`); } catch (error) { console.log('Failed to process codeowner notifications:', error.message); console.error(error); }