Skip to content
Draft
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
92 changes: 92 additions & 0 deletions .github/workflows/danger-comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
name: danger-comment
on:
workflow_run:
workflows: [danger]
types: [completed]

permissions:
actions: read # Required to download the artifact
contents: read # Standard safe default
issues: write # Required to post comments (PRs are Issues in the API)

jobs:
comment:
runs-on: ubuntu-latest
if: github.event.workflow_run.event == 'pull_request'
steps:
- name: Download Danger report
uses: actions/download-artifact@v6
with:
name: danger-report
github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }}
- name: Post or update PR comment
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('danger_report.json', 'utf8'));

if (!report.pr_number) {
console.log('No PR number found in report, skipping comment');
return;
}

let body = '## Danger Report\n\n';

if (report.errors && report.errors.length > 0) {
body += '### Errors\n';
report.errors.forEach(e => body += `- :no_entry_sign: ${e}\n`);
body += '\n';
}

if (report.warnings && report.warnings.length > 0) {
body += '### Warnings\n';
report.warnings.forEach(w => body += `- :warning: ${w}\n`);
body += '\n';
}

if (report.messages && report.messages.length > 0) {
body += '### Messages\n';
report.messages.forEach(m => body += `- :book: ${m}\n`);
body += '\n';
}

if ((!report.errors || report.errors.length === 0) &&
(!report.warnings || report.warnings.length === 0) &&
(!report.messages || report.messages.length === 0)) {
body += ':white_check_mark: All checks passed!';
}

const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: report.pr_number
});

const botComment = comments.find(c =>
c.user.login === 'github-actions[bot]' &&
c.body.includes('## Danger Report')
);

if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: report.pr_number,
body: body
});
}

// Fail if there are errors
if (report.errors && report.errors.length > 0) {
core.setFailed('Danger found errors');
}
24 changes: 17 additions & 7 deletions .github/workflows/danger.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
---
name: danger
on: pull_request
on:
pull_request:
types: [opened, synchronize, reopened]

jobs:
danger:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 100
fetch-depth: 0
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.4
bundler-cache: true
- name: Run Danger
run: |
# the token is public, has public_repo scope and belongs to the grape-bot user owned by @dblock, this is ok
TOKEN=$(echo -n Z2hwX2lYb0dPNXNyejYzOFJyaTV3QUxUdkNiS1dtblFwZTFuRXpmMwo= | base64 --decode)
DANGER_GITHUB_API_TOKEN=$TOKEN bundle exec danger --verbose
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_NUMBER: ${{ github.event.pull_request.number }}
DANGER_REPORT_PATH: danger_report.json
run: bundle exec danger dry_run --base=$BASE_SHA --head=$HEAD_SHA --verbose
- name: Upload Danger report
uses: actions/upload-artifact@v5
with:
name: danger-report
path: danger_report.json
retention-days: 1
40 changes: 39 additions & 1 deletion Dangerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,41 @@
# frozen_string_literal: true

danger.import_dangerfile(gem: 'ruby-grape-danger')
# Inline checks from ruby-grape-danger (avoids plugins requiring GitHub API token)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this, ofc, should be moved to the https://github.com/ruby-grape/danger/blob/master/Dangerfile


has_app_changes = !git.modified_files.grep(%r{^lib/}).empty?
has_spec_changes = !git.modified_files.grep(%r{^spec/}).empty?

warn("There're library changes, but not tests. That's OK as long as you're refactoring existing code.", sticky: false) if has_app_changes && !has_spec_changes

message('We really appreciate pull requests that demonstrate issues, even without a fix. That said, the next step is to try and fix the failing tests!', sticky: false) if !has_app_changes && has_spec_changes

# Simplified changelog check (replaces danger-changelog plugin which requires github.* methods)
# Note: toc.check! from danger-toc plugin removed (not essential for CI)
Copy link
Contributor Author

@numbata numbata Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has_changelog_changes = git.modified_files.include?('CHANGELOG.md') || git.added_files.include?('CHANGELOG.md')
warn('Please update CHANGELOG.md with a description of your changes.', sticky: false) if has_app_changes && !has_changelog_changes

(git.modified_files + git.added_files - %w[Dangerfile]).each do |file|
next unless File.file?(file)

contents = File.read(file)
# rubocop:disable Style/SignalException -- `fail` is Danger's DSL method, not Kernel#fail
if file.start_with?('spec')
fail("`xit` or `fit` left in tests (#{file})") if /^\s*[xf]it\b/.match?(contents)
fail("`fdescribe` left in tests (#{file})") if /^\s*fdescribe\b/.match?(contents)
end
# rubocop:enable Style/SignalException
end

# Output JSON report for GitHub Actions workflow_run to post as PR comment
if ENV['DANGER_REPORT_PATH']
require 'json'

report = {
pr_number: ENV['PR_NUMBER']&.to_i,
errors: violation_report[:errors].map(&:message),
warnings: violation_report[:warnings].map(&:message),
messages: violation_report[:messages].map(&:message)
}

File.write(ENV['DANGER_REPORT_PATH'], JSON.pretty_generate(report))
end