Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .github/workflows/pr-help.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: PR help
# cSpell:ignore otelbot

on:
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to comment on'
required: true
type: number

permissions:
contents: read

env:
COMMENT: ${{ github.event.comment.body }}
PR_NUM: ${{ github.event.inputs.pr_number || github.event.issue.number }}

jobs:
show-help:
name: Show PR actions help
runs-on: ubuntu-latest
if: |
github.event_name == 'workflow_dispatch' ||
(github.event.issue.pull_request && github.event.comment.body == '/help')

permissions:
pull-requests: write

steps:
- uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit

- uses: actions/checkout@v5
with:
fetch-depth: 1

- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc

- uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
id: otelbot-token
with:
app-id: ${{ vars.OTELBOT_APP_ID }}
private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }}

- name: Generate help text
id: help
run: |
# Generate help text and save to file
node scripts/generate-pr-help.js > help.txt

# Read the help text and set as output
# Using heredoc to handle multiline content safely
{
echo 'helptext<<EOF'
cat help.txt
echo EOF
} >> "$GITHUB_OUTPUT"

- name: Post help comment
run: |
gh pr comment $PR_NUM --body "${{ steps.help.outputs.helptext }}"
env:
GH_TOKEN: ${{ steps.otelbot-token.outputs.token }}

- name: Add reaction to trigger comment
if: github.event_name == 'issue_comment'
run: |
# Add a 👀 reaction to acknowledge the command
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
-f content='eyes'
env:
GH_TOKEN: ${{ steps.otelbot-token.outputs.token }}
146 changes: 146 additions & 0 deletions scripts/generate-pr-help.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#!/usr/bin/env node

/**
* Generate PR help text for /help command
* Parses package.json to extract all fix:* commands and generates formatted markdown
*/

const fs = require('fs');
const path = require('path');

// Command descriptions mapping
const COMMAND_DESCRIPTIONS = {
'fix:dict': 'Normalize cspell front matter in markdown files',
'fix:expired': 'Delete expired content files',
'fix:filenames': 'Rename files with underscores to kebab-case',
'fix:format': 'Format code and trim trailing spaces',
'fix:htmltest-config': 'Update htmltest configuration',
'fix:i18n:status': 'Update internationalization status',
'fix:i18n:new': 'Handle new internationalization files',
'fix:i18n': 'Run all i18n fixes (new + status)',
'fix:markdown': 'Fix markdown linting issues and trailing spaces',
'fix:refcache:refresh': 'Refresh reference cache (prune entries)',
'fix:refcache': 'Prune reference cache and check links',
'fix:submodule': 'Pin submodules and update semconv mounts',
'fix:text': 'Fix textlint issues',
'fix:all': 'Run all fix commands (except i18n)',
fix: 'Run all fix commands',
};

// Common usage examples
const USAGE_EXAMPLES = [
{
command: '/fix:format',
description: 'Format all code files and fix spacing issues',
when: 'Use when CI shows formatting errors',
},
{
command: '/fix:markdown',
description: 'Fix markdown linting issues',
when: 'Use when markdownlint checks fail',
},
{
command: '/fix:refcache',
description: 'Update the reference cache for link checking',
when: 'Use when link checker shows stale cached results',
},
{
command: '/fix:submodule',
description: 'Update git submodules to latest versions',
when: 'Use when submodule updates are needed',
},
];

// Troubleshooting tips
const TROUBLESHOOTING = [
{
issue: 'Command had no effect',
solution:
'The command may have run successfully but found nothing to fix. Check the workflow run logs for details.',
},
{
issue: 'Command failed with errors',
solution:
'Check the workflow run logs for specific error messages. You may need to fix issues manually.',
},
{
issue: 'Patch too large',
solution:
'The changes exceed 1MB limit. Break into smaller fixes or apply changes manually.',
},
{
issue: 'Patch failed to apply',
solution:
'Conflicts with recent commits. Pull latest changes and run the fix locally.',
},
];

function generateHelpText() {
// Read package.json
const packageJsonPath = path.join(__dirname, '..', 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));

// Extract all fix:* commands
const fixCommands = Object.keys(packageJson.scripts)
.filter((script) => script.startsWith('fix:'))
.sort();

// Generate markdown
let markdown = `## 🤖 PR Actions Bot - Help\n\n`;
markdown += `This bot can automatically fix common issues in your PR by responding to slash commands.\n\n`;

// Available Commands
markdown += `### 📋 Available Commands\n\n`;
markdown += `Comment on this PR with any of these commands:\n\n`;

fixCommands.forEach((cmd) => {
const description = COMMAND_DESCRIPTIONS[cmd] || 'Run fix command';
markdown += `- \`/${cmd}\` - ${description}\n`;
});

// Usage Examples
markdown += `\n### 💡 Usage Examples\n\n`;
USAGE_EXAMPLES.forEach((example) => {
markdown += `**\`${example.command}\`**\n`;
markdown += `- ${example.description}\n`;
markdown += `- _${example.when}_\n\n`;
});

// How it works
markdown += `### ⚙️ How It Works\n\n`;
markdown += `1. Comment with a \`/fix:*\` command on this PR\n`;
markdown += `2. The bot runs the command in a secure environment\n`;
markdown += `3. If changes are needed, they're automatically committed to your branch\n`;
markdown += `4. You'll get a success/failure notification\n\n`;

// Troubleshooting
markdown += `### 🔧 Troubleshooting\n\n`;
TROUBLESHOOTING.forEach((tip) => {
markdown += `**${tip.issue}**\n`;
markdown += `${tip.solution}\n\n`;
});

// Documentation
markdown += `### 📚 Documentation\n\n`;
markdown += `- [Contributing Guide](https://opentelemetry.io/docs/contributing/)\n`;
markdown += `- [Project README](../CONTRIBUTING.md)\n`;
markdown += `- [PR Actions Workflow](.github/workflows/pr-actions.yml)\n\n`;

markdown += `---\n`;
markdown += `_💬 Need more help? Ask in the PR comments or reach out to maintainers._\n`;

return markdown;
}

// Main execution
if (require.main === module) {
try {
const helpText = generateHelpText();
console.log(helpText);
} catch (error) {
console.error('Error generating help text:', error.message);
process.exit(1);
}
}

module.exports = { generateHelpText };