Last active
October 21, 2025 15:31
-
-
Save Moose0621/0582fc085bc2cc746923511ca1c626df to your computer and use it in GitHub Desktop.
get-pdf.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Usage: ./generate_report.sh <owner> <repo_name> | |
| # Example: ./generate_report.sh octocat Hello-World | |
| # Note: Requires GitHub CLI (gh) to be installed and authenticated | |
| set -e | |
| OWNER="$1" | |
| REPO_NAME="$2" | |
| OUTPUT_DIR="./reports" | |
| TEMPLATE="summary.html" | |
| if [ -z "$OWNER" ] || [ -z "$REPO_NAME" ]; then | |
| echo "Usage: $0 <owner> <repo_name>" | |
| echo "Example: $0 octocat Hello-World" | |
| echo "Note: Requires GitHub CLI (gh) to be installed and authenticated" | |
| echo "Run 'gh auth login' to authenticate with GitHub" | |
| exit 1 | |
| fi | |
| REPO="$OWNER/$REPO_NAME" | |
| # Check if GitHub CLI is installed and authenticated | |
| if ! command -v gh &> /dev/null; then | |
| echo "Error: GitHub CLI (gh) is not installed" | |
| echo "Install it from: https://cli.github.com/" | |
| exit 1 | |
| fi | |
| # Always use github.com for this script | |
| HOSTNAME="github.com" | |
| GH_HOST_FLAG="--hostname github.com" | |
| # Check authentication for GitHub.com | |
| if ! gh auth status $GH_HOST_FLAG &> /dev/null; then | |
| echo "Error: GitHub CLI is not authenticated for $HOSTNAME" | |
| echo "Run 'gh auth login $GH_HOST_FLAG' to authenticate with $HOSTNAME" | |
| exit 1 | |
| fi | |
| mkdir -p "$OUTPUT_DIR" | |
| # Step 1: Fetch latest SARIF results from GitHub code scanning | |
| echo "Fetching latest SARIF results from GitHub code scanning..." | |
| # Get all code scanning alerts and extract SARIF data | |
| echo "Fetching code scanning alerts..." | |
| if ! CODE_SCANNING_ALERTS=$(gh api repos/$REPO/code-scanning/alerts $GH_HOST_FLAG 2>&1); then | |
| echo "Warning: Could not fetch code scanning alerts: $CODE_SCANNING_ALERTS" | |
| CODE_SCANNING_ALERTS="[]" | |
| fi | |
| # Get the latest code scanning analyses to fetch SARIF files | |
| echo "Fetching code scanning analyses..." | |
| if ! ANALYSES=$(gh api repos/$REPO/code-scanning/analyses $GH_HOST_FLAG 2>&1); then | |
| echo "Warning: Could not fetch code scanning analyses: $ANALYSES" | |
| ANALYSES="[]" | |
| fi | |
| # Download the latest SARIF file from the most recent analysis | |
| LATEST_ANALYSIS_ID=$(echo "$ANALYSES" | jq -r '.[0].id // empty' 2>/dev/null) | |
| if [ -n "$LATEST_ANALYSIS_ID" ] && [ "$LATEST_ANALYSIS_ID" != "null" ]; then | |
| echo "Downloading SARIF file from analysis ID: $LATEST_ANALYSIS_ID" | |
| if SARIF_DATA=$(gh api repos/$REPO/code-scanning/analyses/$LATEST_ANALYSIS_ID --header "Accept: application/sarif+json" $GH_HOST_FLAG 2>&1); then | |
| echo "$SARIF_DATA" > "$OUTPUT_DIR/latest.sarif" | |
| # Convert SARIF to JSON array format expected by the report | |
| SARIF_JSON="[$SARIF_DATA]" | |
| else | |
| echo "Warning: Could not download SARIF data: $SARIF_DATA" | |
| SARIF_JSON="[]" | |
| fi | |
| else | |
| echo "No code scanning analyses found. Using empty SARIF data." | |
| SARIF_JSON="[]" | |
| fi | |
| # Step 2: Fetch additional security data from GitHub using GitHub CLI | |
| echo "Fetching code scanning alerts..." | |
| if ! CODE_SCANNING_ALERTS=$(gh api repos/$REPO/code-scanning/alerts $GH_HOST_FLAG 2>&1); then | |
| echo "Warning: Could not fetch code scanning alerts: $CODE_SCANNING_ALERTS" | |
| CODE_SCANNING_ALERTS="[]" | |
| fi | |
| echo "Fetching dependency and vulnerability data using GitHub CLI..." | |
| if ! DEPENDENCY_JSON=$(gh api graphql $GH_HOST_FLAG -f query=' | |
| query($owner: String!, $name: String!) { | |
| repository(owner: $owner, name: $name) { | |
| dependencyGraphManifests(first: 100) { | |
| edges { | |
| node { | |
| filename | |
| dependencies(first: 100) { | |
| nodes { | |
| packageName | |
| packageManager | |
| requirements | |
| } | |
| } | |
| } | |
| } | |
| } | |
| vulnerabilityAlerts(first: 100) { | |
| nodes { | |
| vulnerableManifestFilename | |
| securityVulnerability { | |
| package { | |
| name | |
| ecosystem | |
| } | |
| severity | |
| } | |
| } | |
| } | |
| } | |
| } | |
| ' -F owner="$OWNER" -F name="$REPO_NAME" 2>&1); then | |
| echo "Warning: Could not fetch dependency data: $DEPENDENCY_JSON" | |
| DEPENDENCY_JSON='{"data":{"repository":{"dependencyGraphManifests":{"edges":[]},"vulnerabilityAlerts":{"nodes":[]}}}}' | |
| fi | |
| # Fetch secret scanning alerts if available | |
| echo "Fetching secret scanning alerts..." | |
| if ! SECRET_SCANNING_ALERTS=$(gh api repos/$REPO/secret-scanning/alerts $GH_HOST_FLAG 2>/dev/null); then | |
| echo "Note: Secret scanning alerts not available or accessible" | |
| SECRET_SCANNING_ALERTS="[]" | |
| fi | |
| # Step 3: Combine all data into a comprehensive data.json | |
| echo "Combining report data..." | |
| # Write SARIF data to temporary file to avoid argument list too long | |
| echo "$SARIF_JSON" > "$OUTPUT_DIR/temp_sarif.json" | |
| jq -n \ | |
| --slurpfile sarif "$OUTPUT_DIR/temp_sarif.json" \ | |
| --argjson codeScanning "$CODE_SCANNING_ALERTS" \ | |
| --argjson dependency "$DEPENDENCY_JSON" \ | |
| --argjson secretScanning "$SECRET_SCANNING_ALERTS" \ | |
| --arg repo "$REPO" \ | |
| --arg owner "$OWNER" \ | |
| --arg repoName "$REPO_NAME" \ | |
| --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ | |
| '{ | |
| metadata: { | |
| repository: $repo, | |
| owner: $owner, | |
| repositoryName: $repoName, | |
| generatedAt: $timestamp | |
| }, | |
| sarifReports: $sarif, | |
| codeScanning: $codeScanning, | |
| dependencies: $dependency, | |
| secretScanning: $secretScanning | |
| }' > "$OUTPUT_DIR/data.json" | |
| # Clean up temporary file | |
| rm "$OUTPUT_DIR/temp_sarif.json" | |
| # Step 4: Generate a comprehensive HTML report | |
| if [ ! -f "$TEMPLATE" ] || ! command -v node &> /dev/null; then | |
| echo "Generating comprehensive HTML report..." | |
| # Extract summary statistics | |
| CODE_SCANNING_COUNT=$(echo "$CODE_SCANNING_ALERTS" | jq 'length') | |
| SECRET_SCANNING_COUNT=$(echo "$SECRET_SCANNING_ALERTS" | jq 'length') | |
| # Count by severity | |
| HIGH_COUNT=$(echo "$CODE_SCANNING_ALERTS" | jq '[.[] | select(.rule.security_severity_level == "high")] | length') | |
| MEDIUM_COUNT=$(echo "$CODE_SCANNING_ALERTS" | jq '[.[] | select(.rule.security_severity_level == "medium")] | length') | |
| LOW_COUNT=$(echo "$CODE_SCANNING_ALERTS" | jq '[.[] | select(.rule.security_severity_level == "low")] | length') | |
| CRITICAL_COUNT=$(echo "$CODE_SCANNING_ALERTS" | jq '[.[] | select(.rule.security_severity_level == "critical")] | length') | |
| # Generate HTML with actual data | |
| cat > "$OUTPUT_DIR/summary.html" << EOF | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Security Report - $REPO</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| background: #f8f9fa; | |
| color: #333; | |
| line-height: 1.6; | |
| } | |
| .container { max-width: 1200px; margin: 0 auto; padding: 20px; } | |
| .header { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 30px; | |
| border-radius: 10px; | |
| margin-bottom: 30px; | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.1); | |
| } | |
| .header h1 { font-size: 2.5em; margin-bottom: 10px; } | |
| .header .repo { font-size: 1.2em; opacity: 0.9; } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 20px; | |
| margin-bottom: 30px; | |
| } | |
| .stat-card { | |
| background: white; | |
| padding: 25px; | |
| border-radius: 10px; | |
| text-align: center; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.08); | |
| border-left: 4px solid #ddd; | |
| } | |
| .stat-card.critical { border-left-color: #dc3545; } | |
| .stat-card.high { border-left-color: #fd7e14; } | |
| .stat-card.medium { border-left-color: #ffc107; } | |
| .stat-card.low { border-left-color: #6f42c1; } | |
| .stat-card.info { border-left-color: #17a2b8; } | |
| .stat-number { font-size: 2.5em; font-weight: bold; margin-bottom: 5px; } | |
| .stat-label { color: #666; font-size: 0.9em; text-transform: uppercase; letter-spacing: 1px; } | |
| .section { | |
| background: white; | |
| margin-bottom: 30px; | |
| border-radius: 10px; | |
| overflow: hidden; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.08); | |
| } | |
| .section-header { | |
| background: #495057; | |
| color: white; | |
| padding: 20px; | |
| font-size: 1.3em; | |
| font-weight: 600; | |
| } | |
| .section-content { padding: 20px; } | |
| .alert-item { | |
| padding: 15px; | |
| margin: 10px 0; | |
| border-radius: 8px; | |
| border-left: 4px solid #ddd; | |
| background: #f8f9fa; | |
| } | |
| .alert-item.critical { border-left-color: #dc3545; background: #f8d7da; } | |
| .alert-item.high { border-left-color: #fd7e14; background: #ffeaa7; } | |
| .alert-item.medium { border-left-color: #ffc107; background: #fff3cd; } | |
| .alert-item.low { border-left-color: #6f42c1; background: #e2e3f3; } | |
| .alert-title { font-weight: 600; margin-bottom: 8px; color: #333; } | |
| .alert-description { color: #666; margin-bottom: 8px; } | |
| .alert-location { | |
| font-family: 'Monaco', 'Menlo', monospace; | |
| font-size: 0.85em; | |
| color: #495057; | |
| background: #e9ecef; | |
| padding: 5px 8px; | |
| border-radius: 4px; | |
| display: inline-block; | |
| } | |
| .severity-badge { | |
| display: inline-block; | |
| padding: 4px 8px; | |
| border-radius: 12px; | |
| font-size: 0.75em; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| margin-left: 10px; | |
| } | |
| .severity-critical { background: #dc3545; color: white; } | |
| .severity-high { background: #fd7e14; color: white; } | |
| .severity-medium { background: #ffc107; color: black; } | |
| .severity-low { background: #6f42c1; color: white; } | |
| .no-data { text-align: center; color: #666; padding: 40px; } | |
| .timestamp { text-align: right; color: #666; margin-top: 30px; font-size: 0.9em; } | |
| .rule-tags { margin-top: 8px; } | |
| .tag { | |
| display: inline-block; | |
| background: #e9ecef; | |
| color: #495057; | |
| padding: 2px 6px; | |
| border-radius: 3px; | |
| font-size: 0.7em; | |
| margin-right: 5px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>🔒 Security Report</h1> | |
| <div class="repo">Repository: <strong>$REPO</strong></div> | |
| <div class="repo">Generated: <strong>$(date)</strong></div> | |
| </div> | |
| <div class="stats-grid"> | |
| <div class="stat-card critical"> | |
| <div class="stat-number">$CRITICAL_COUNT</div> | |
| <div class="stat-label">Critical</div> | |
| </div> | |
| <div class="stat-card high"> | |
| <div class="stat-number">$HIGH_COUNT</div> | |
| <div class="stat-label">High</div> | |
| </div> | |
| <div class="stat-card medium"> | |
| <div class="stat-number">$MEDIUM_COUNT</div> | |
| <div class="stat-label">Medium</div> | |
| </div> | |
| <div class="stat-card low"> | |
| <div class="stat-number">$LOW_COUNT</div> | |
| <div class="stat-label">Low</div> | |
| </div> | |
| <div class="stat-card info"> | |
| <div class="stat-number">$SECRET_SCANNING_COUNT</div> | |
| <div class="stat-label">Secrets Found</div> | |
| </div> | |
| </div> | |
| <div class="section"> | |
| <div class="section-header">📊 Code Scanning Alerts ($CODE_SCANNING_COUNT total)</div> | |
| <div class="section-content"> | |
| EOF | |
| # Add code scanning alerts to HTML | |
| if [ "$CODE_SCANNING_COUNT" -gt 0 ]; then | |
| echo "$CODE_SCANNING_ALERTS" | jq -r '.[] | @json' | while IFS= read -r alert; do | |
| ALERT_NUMBER=$(echo "$alert" | jq -r '.number') | |
| ALERT_RULE=$(echo "$alert" | jq -r '.rule.description') | |
| ALERT_SEVERITY=$(echo "$alert" | jq -r '.rule.security_severity_level // "unknown"') | |
| ALERT_PATH=$(echo "$alert" | jq -r '.most_recent_instance.location.path') | |
| ALERT_LINE=$(echo "$alert" | jq -r '.most_recent_instance.location.start_line') | |
| ALERT_TAGS=$(echo "$alert" | jq -r '.rule.tags[]?' | head -3 | tr '\n' ' ') | |
| cat >> "$OUTPUT_DIR/summary.html" << EOF | |
| <div class="alert-item $ALERT_SEVERITY"> | |
| <div class="alert-title"> | |
| $ALERT_RULE | |
| <span class="severity-badge severity-$ALERT_SEVERITY">$ALERT_SEVERITY</span> | |
| </div> | |
| <div class="alert-location">$ALERT_PATH:$ALERT_LINE</div> | |
| <div class="rule-tags"> | |
| EOF | |
| if [ -n "$ALERT_TAGS" ]; then | |
| echo "$ALERT_TAGS" | tr ' ' '\n' | while read -r tag; do | |
| if [ -n "$tag" ]; then | |
| echo " <span class=\"tag\">$tag</span>" >> "$OUTPUT_DIR/summary.html" | |
| fi | |
| done | |
| fi | |
| cat >> "$OUTPUT_DIR/summary.html" << EOF | |
| </div> | |
| </div> | |
| EOF | |
| done | |
| else | |
| echo ' <div class="no-data">✅ No code scanning alerts found</div>' >> "$OUTPUT_DIR/summary.html" | |
| fi | |
| cat >> "$OUTPUT_DIR/summary.html" << EOF | |
| </div> | |
| </div> | |
| <div class="section"> | |
| <div class="section-header">🔐 Secret Scanning Alerts ($SECRET_SCANNING_COUNT total)</div> | |
| <div class="section-content"> | |
| EOF | |
| # Add secret scanning alerts to HTML | |
| if [ "$SECRET_SCANNING_COUNT" -gt 0 ]; then | |
| echo "$SECRET_SCANNING_ALERTS" | jq -r '.[] | @json' | while IFS= read -r secret; do | |
| SECRET_TYPE=$(echo "$secret" | jq -r '.secret_type_display_name // .secret_type') | |
| SECRET_PATH=$(echo "$secret" | jq -r '.locations[0].details.path // "Unknown"') | |
| SECRET_STATE=$(echo "$secret" | jq -r '.state') | |
| cat >> "$OUTPUT_DIR/summary.html" << EOF | |
| <div class="alert-item high"> | |
| <div class="alert-title"> | |
| $SECRET_TYPE detected | |
| <span class="severity-badge severity-high">$SECRET_STATE</span> | |
| </div> | |
| <div class="alert-location">$SECRET_PATH</div> | |
| </div> | |
| EOF | |
| done | |
| else | |
| echo ' <div class="no-data">✅ No secrets detected</div>' >> "$OUTPUT_DIR/summary.html" | |
| fi | |
| cat >> "$OUTPUT_DIR/summary.html" << EOF | |
| </div> | |
| </div> | |
| <div class="timestamp"> | |
| Report generated on $(date) | | |
| <a href="https://github.com/$REPO/security" target="_blank">View on GitHub</a> | |
| </div> | |
| </div> | |
| </body> | |
| </html> | |
| EOF | |
| else | |
| # Step 4: Render the HTML report using a Node.js script (if available) | |
| echo "Rendering HTML from template..." | |
| node render.js "$OUTPUT_DIR/data.json" "$TEMPLATE" > "$OUTPUT_DIR/summary.html" | |
| fi | |
| # Step 5: Generate PDF with multiple fallback methods | |
| echo "Generating PDF report..." | |
| # Method 1: Try wkhtmltopdf if available | |
| if command -v wkhtmltopdf &> /dev/null; then | |
| echo "Using wkhtmltopdf for PDF generation..." | |
| wkhtmltopdf --page-size A4 --margin-top 0.75in --margin-right 0.75in --margin-bottom 0.75in --margin-left 0.75in \ | |
| --disable-smart-shrinking --print-media-type "$OUTPUT_DIR/summary.html" "$OUTPUT_DIR/summary.pdf" | |
| echo "PDF Report: $OUTPUT_DIR/summary.pdf" | |
| # Method 2: Try Chrome/Chromium headless | |
| elif command -v google-chrome &> /dev/null; then | |
| echo "Using Google Chrome for PDF generation..." | |
| google-chrome --headless --disable-gpu --no-sandbox --print-to-pdf="$OUTPUT_DIR/summary.pdf" \ | |
| --virtual-time-budget=5000 "file://$(pwd)/$OUTPUT_DIR/summary.html" | |
| echo "PDF Report: $OUTPUT_DIR/summary.pdf" | |
| elif command -v chromium &> /dev/null; then | |
| echo "Using Chromium for PDF generation..." | |
| chromium --headless --disable-gpu --no-sandbox --print-to-pdf="$OUTPUT_DIR/summary.pdf" \ | |
| --virtual-time-budget=5000 "file://$(pwd)/$OUTPUT_DIR/summary.html" | |
| echo "PDF Report: $OUTPUT_DIR/summary.pdf" | |
| elif command -v chromium-browser &> /dev/null; then | |
| echo "Using Chromium browser for PDF generation..." | |
| chromium-browser --headless --disable-gpu --no-sandbox --print-to-pdf="$OUTPUT_DIR/summary.pdf" \ | |
| --virtual-time-budget=5000 "file://$(pwd)/$OUTPUT_DIR/summary.html" | |
| echo "PDF Report: $OUTPUT_DIR/summary.pdf" | |
| # Method 3: Create Node.js PDF generator if Node.js is available | |
| elif command -v node &> /dev/null; then | |
| echo "Setting up Node.js PDF generation..." | |
| # Check if puppeteer is installed, if not try to install it | |
| if ! node -e "require('puppeteer')" 2>/dev/null; then | |
| echo "Installing puppeteer for PDF generation..." | |
| npm install puppeteer 2>/dev/null || { | |
| echo "Warning: Could not install puppeteer automatically" | |
| echo "To enable PDF generation, run: npm install puppeteer" | |
| } | |
| fi | |
| # Create PDF generator script | |
| cat > html2pdf.js << 'EOF' | |
| const puppeteer = require('puppeteer'); | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| (async () => { | |
| try { | |
| const htmlFile = process.argv[2]; | |
| const pdfFile = process.argv[3]; | |
| if (!htmlFile || !pdfFile) { | |
| console.error('Usage: node html2pdf.js <input.html> <output.pdf>'); | |
| process.exit(1); | |
| } | |
| const browser = await puppeteer.launch({ | |
| headless: true, | |
| args: ['--no-sandbox', '--disable-setuid-sandbox'] | |
| }); | |
| const page = await browser.newPage(); | |
| const htmlContent = fs.readFileSync(htmlFile, 'utf-8'); | |
| await page.setContent(htmlContent, { waitUntil: 'networkidle0' }); | |
| await page.pdf({ | |
| path: pdfFile, | |
| format: 'A4', | |
| margin: { | |
| top: '20mm', | |
| right: '20mm', | |
| bottom: '20mm', | |
| left: '20mm' | |
| }, | |
| printBackground: true | |
| }); | |
| await browser.close(); | |
| console.log(`PDF generated successfully: ${pdfFile}`); | |
| } catch (error) { | |
| console.error('Error generating PDF:', error.message); | |
| process.exit(1); | |
| } | |
| })(); | |
| EOF | |
| # Try to generate PDF with Node.js | |
| if node html2pdf.js "$OUTPUT_DIR/summary.html" "$OUTPUT_DIR/summary.pdf" 2>/dev/null; then | |
| echo "PDF Report: $OUTPUT_DIR/summary.pdf" | |
| rm html2pdf.js | |
| else | |
| echo "Warning: PDF generation with Node.js failed" | |
| rm html2pdf.js 2>/dev/null | |
| fi | |
| # Method 4: Fallback - create a simple text-based PDF instruction | |
| else | |
| echo "Creating PDF generation instructions..." | |
| cat > "$OUTPUT_DIR/generate_pdf.md" << EOF | |
| # PDF Generation Instructions | |
| To generate a PDF from the HTML report, you can use one of these methods: | |
| ## Option 1: Install wkhtmltopdf | |
| \`\`\`bash | |
| # On macOS: | |
| brew install wkhtmltopdf | |
| # On Ubuntu/Debian: | |
| sudo apt-get install wkhtmltopdf | |
| # Then run: | |
| wkhtmltopdf summary.html summary.pdf | |
| \`\`\` | |
| ## Option 2: Use Chrome/Chromium | |
| \`\`\`bash | |
| # Using Google Chrome: | |
| google-chrome --headless --disable-gpu --print-to-pdf=summary.pdf file://\$(pwd)/summary.html | |
| # Using Chromium: | |
| chromium --headless --disable-gpu --print-to-pdf=summary.pdf file://\$(pwd)/summary.html | |
| \`\`\` | |
| ## Option 3: Use Node.js with Puppeteer | |
| \`\`\`bash | |
| npm install puppeteer | |
| # Then use the html2pdf.js script provided | |
| \`\`\` | |
| ## Option 4: Print from Browser | |
| 1. Open summary.html in your web browser | |
| 2. Press Ctrl+P (or Cmd+P on Mac) | |
| 3. Select "Save as PDF" as the destination | |
| 4. Adjust settings as needed and save | |
| EOF | |
| echo "PDF generation tools not found. See generate_pdf.md for instructions." | |
| echo "Instructions: $OUTPUT_DIR/generate_pdf.md" | |
| fi | |
| echo "Security report generated successfully!" | |
| echo "HTML Report: $OUTPUT_DIR/summary.html" | |
| echo "Data file: $OUTPUT_DIR/data.json" | |
| if [ -f "$OUTPUT_DIR/latest.sarif" ]; then | |
| echo "SARIF file: $OUTPUT_DIR/latest.sarif" | |
| fi | |
| if [ -f "$OUTPUT_DIR/summary.pdf" ]; then | |
| echo "PDF Report: $OUTPUT_DIR/summary.pdf" | |
| fi | |
| if [ -f "$OUTPUT_DIR/generate_pdf.md" ]; then | |
| echo "PDF instructions: $OUTPUT_DIR/generate_pdf.md" | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment