Skip to content
Merged
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
9 changes: 9 additions & 0 deletions .github/workflows/lint-automation.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
name: 🔧 Automated Lint Issue Detection

on:
# Run on push to main/develop branches
push:
branches: [ main, develop ]
# Run on pull requests to main
pull_request:
branches: [ main ]
# Run after Next.js workflow completion on main branch
workflow_run:
workflows: ["Deploy Next.js site to Pages"]
types:
- completed
branches: [ main ]
# Manual trigger for testing
workflow_dispatch:
inputs:
create_issues:
Expand Down
116 changes: 2 additions & 114 deletions .github/workflows/nextjs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,120 +86,8 @@ jobs:
with:
path: ./out

# Lint analysis job - runs when build fails
lint-analysis-on-failure:
runs-on: ubuntu-latest
needs: build
if: needs.build.outputs.build-success != 'true'
name: 🔍 Analyze Build Failures

steps:
- name: 📦 Checkout repository
uses: actions/checkout@v4

- name: 🟢 Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: 📥 Install dependencies
run: npm ci

- name: 🔍 Run comprehensive lint analysis
id: analyze
run: |
echo "🔍 Running lint analysis to identify build failures..."

# Run the enhanced lint analyzer that includes TypeScript checks
npx tsx scripts/lint-automation/lint-analyzer.ts || true

# Check if we generated a report and if there are any issues
if [ -f "lint-analysis-report.json" ]; then
ISSUES_COUNT=$(jq '.summary.totalIssues' lint-analysis-report.json)
echo "issues-count=${ISSUES_COUNT}" >> $GITHUB_OUTPUT

if [ "${ISSUES_COUNT}" -gt "0" ]; then
echo "has-issues=true" >> $GITHUB_OUTPUT
echo "📊 Found ${ISSUES_COUNT} issues causing build failure"
else
echo "has-issues=false" >> $GITHUB_OUTPUT
echo "🤔 Build failed but no lint/TypeScript issues found"
fi
else
echo "has-issues=false" >> $GITHUB_OUTPUT
echo "issues-count=0" >> $GITHUB_OUTPUT
echo "❌ Could not analyze build failure"
fi

- name: 📄 Upload build failure analysis
if: steps.analyze.outputs.has-issues == 'true'
uses: actions/upload-artifact@v4
with:
name: build-failure-analysis-${{ github.run_number }}
path: |
lint-analysis-report.json
lint-analysis-report.md
retention-days: 30

- name: 🎯 Create GitHub issues for build failures
if: steps.analyze.outputs.has-issues == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }}
run: |
echo "📝 Creating GitHub issues for build failures..."
npx tsx scripts/lint-automation/github-issue-creator.ts

- name: 💬 Add deployment failure comment
if: steps.analyze.outputs.has-issues == 'true'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');

try {
const report = JSON.parse(fs.readFileSync('lint-analysis-report.json', 'utf8'));

// Find the most recent commit
const { data: commits } = await github.rest.repos.listCommits({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.sha,
per_page: 1
});

const comment = `## 🚨 Next.js Deployment Failed

**Build failed with ${report.summary.totalIssues} issues:**
- ❌ ${report.summary.errorCount} errors
- ⚠️ ${report.summary.warningCount} warnings
- 📁 ${report.summary.affectedFiles} files affected

### 🔧 Most Common Issues:
${report.summary.commonPatterns.map(p => `- ${p}`).join('\n')}

### 🎯 Immediate Actions Required:
${report.recommendations.immediate.map(r => `- [ ] ${r}`).join('\n')}

**📊 Full analysis report:** See workflow artifacts for detailed breakdown.

**🤖 GitHub Issues:** Individual issues have been created for each problem to track resolution.

---
*Automated deployment failure analysis - Commit: ${context.sha.substring(0, 7)}*`;

// Create a commit comment
await github.rest.repos.createCommitComment({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: context.sha,
body: comment
});
} catch (error) {
console.log('Could not post commit comment:', error.message);
}
# Note: Lint analysis is now handled by the dedicated lint-automation workflow
# This prevents duplicate issue creation and ensures proper workflow coordination

# Deployment job - only runs if build succeeds
deploy:
Expand Down
125 changes: 112 additions & 13 deletions scripts/lint-automation/github-issue-creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,11 +459,13 @@ All instances of \`${ruleId}\` violations have been fixed. This issue is now aut
const category = fileIssues[0].category;
const severity = fileIssues.some(issue => issue.severity === 'error') ? 'error' : 'warning';

// Create title using filename as requested by user
// Create enhanced title with region context for API verify files
const enhancedTitle = this.generateEnhancedTitle(filePath, fileName);

groups.push({
category,
title: fileName,
body: this.generateFileIssueBody(fileName, filePath, fileIssues),
title: enhancedTitle,
body: this.generateFileIssueBody(enhancedTitle, filePath, fileIssues),
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

The generateFileIssueBody method is being called with enhancedTitle as the first parameter, but this might not be the intended behavior. Based on the context, the first parameter should likely be the original fileName since the body generation might expect the actual filename rather than the enhanced display title.

Suggested change
body: this.generateFileIssueBody(enhancedTitle, filePath, fileIssues),
body: this.generateFileIssueBody(fileName, filePath, fileIssues),

Copilot uses AI. Check for mistakes.
labels: [
'lint',
'code-quality',
Expand All @@ -477,6 +479,67 @@ All instances of \`${ruleId}\` violations have been fixed. This issue is now aut
return groups;
}

/**
* Generates an enhanced title that includes region context for API verify files
* @param filePath - Full path to the file
* @param fileName - Base filename
* @returns Enhanced title with region context if applicable
*/
private generateEnhancedTitle(filePath: string, fileName: string): string {
// Check if this is a file in the API verify directory structure
const apiVerifyMatch = filePath.match(/app\/api\/verify\/([^\/]+)\/(.+)$/);

if (apiVerifyMatch) {
const regionFolder = apiVerifyMatch[1];
const fileInRegion = apiVerifyMatch[2];

// Convert region folder name to proper case for display
// e.g., "arizona" -> "Arizona", "districtofcolumbia" -> "District of Columbia"
const regionName = this.formatRegionName(regionFolder);

return `${regionName} - ${fileInRegion}`;
}

// For non-API verify files, return the original filename
return fileName;
}

/**
* Formats region folder names into proper display names
* @param regionFolder - Raw folder name (e.g., "arizona", "districtofcolumbia")
* @returns Formatted region name (e.g., "Arizona", "District of Columbia")
*/
private formatRegionName(regionFolder: string): string {
// Handle special cases
const specialCases: Record<string, string> = {
'districtofcolumbia': 'District of Columbia',
'newhampshire': 'New Hampshire',
'newjersey': 'New Jersey',
'newmexico': 'New Mexico',
'newyork': 'New York',
'northcarolina': 'North Carolina',
'northdakota': 'North Dakota',
'rhodeisland': 'Rhode Island',
'southcarolina': 'South Carolina',
'southdakota': 'South Dakota',
'westvirginia': 'West Virginia',
'britishcolumbia': 'British Columbia',
'newbrunswick': 'New Brunswick',
'newfoundland&labrador': 'Newfoundland & Labrador',
'novascotia': 'Nova Scotia',
'princeedwardisland': 'Prince Edward Island',
'puertorico': 'Puerto Rico'
};
Comment on lines +514 to +532
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

[nitpick] The hardcoded mapping of region names could be moved to a configuration file or constants module for better maintainability. This would make it easier to add new regions or modify existing ones without changing the core logic.

Copilot uses AI. Check for mistakes.

// Check for special cases first
if (specialCases[regionFolder.toLowerCase()]) {
return specialCases[regionFolder.toLowerCase()];
}

// For regular cases, just capitalize the first letter
return regionFolder.charAt(0).toUpperCase() + regionFolder.slice(1);
}

private generateSummaryIssueBody(report: IssueReport): string {
let body = `## 📊 Lint Analysis Summary\n\n`;

Expand Down Expand Up @@ -681,14 +744,18 @@ All instances of \`${ruleId}\` violations have been fixed. This issue is now aut
return body;
}

async checkExistingFileIssue(fileName: string): Promise<{ number: number; title: string; body: string } | null> {
async checkExistingFileIssue(enhancedTitle: string): Promise<{ number: number; title: string; body: string } | null> {
if (!this.token) return null;

try {
// Search for issues with the filename as title
const searchQuery = `repo:${this.owner}/${this.repo}+is:issue+is:open+"${fileName}"+label:lint+label:automated`;
// Search for issues with the enhanced title or just the filename
// This handles both old format (just filename) and new format (Region - filename)
const fileNamePart = enhancedTitle.includes(' - ') ? enhancedTitle.split(' - ')[1] : enhancedTitle;

const response = await fetch(
// First try exact match with enhanced title
let searchQuery = `repo:${this.owner}/${this.repo}+is:issue+is:open+"${enhancedTitle}"+label:lint+label:automated`;

let response = await fetch(
`${this.apiBase}/search/issues?q=${encodeURIComponent(searchQuery)}`,
{
headers: {
Expand All @@ -703,9 +770,9 @@ All instances of \`${ruleId}\` violations have been fixed. This issue is now aut
const data = await response.json();
if (data.total_count > 0) {
// Find exact title match (case-insensitive)
const exactMatch = data.items.find((issue: any) => issue.title.toLowerCase() === fileName.toLowerCase());
const exactMatch = data.items.find((issue: any) => issue.title.toLowerCase() === enhancedTitle.toLowerCase());
if (exactMatch) {
console.log(`🔍 Found existing file-based issue for ${fileName}: #${exactMatch.number}`);
console.log(`🔍 Found existing file-based issue for ${enhancedTitle}: #${exactMatch.number}`);
return {
number: exactMatch.number,
title: exactMatch.title,
Expand All @@ -714,14 +781,46 @@ All instances of \`${ruleId}\` violations have been fixed. This issue is now aut
}
}
}

// If no exact match found, try searching for just the filename part (for backward compatibility)
if (fileNamePart !== enhancedTitle) {
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

This condition could be optimized by checking if the enhanced title contains ' - ' before making the API call, since the fileNamePart extraction logic already handles this case. Consider moving this check earlier to avoid unnecessary API calls when the enhanced title doesn't have region context.

Suggested change
if (fileNamePart !== enhancedTitle) {
if (enhancedTitle.includes(' - ')) {

Copilot uses AI. Check for mistakes.
searchQuery = `repo:${this.owner}/${this.repo}+is:issue+is:open+"${fileNamePart}"+label:lint+label:automated`;

response = await fetch(
`${this.apiBase}/search/issues?q=${encodeURIComponent(searchQuery)}`,
{
headers: {
'Authorization': `Bearer ${this.token}`,
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'ClearView-Lint-Automation'
}
}
);

if (response.ok) {
const data = await response.json();
if (data.total_count > 0) {
// Find exact match with just the filename (for migration from old format)
const exactMatch = data.items.find((issue: any) => issue.title.toLowerCase() === fileNamePart.toLowerCase());
if (exactMatch) {
console.log(`🔍 Found existing file-based issue (old format) for ${fileNamePart}: #${exactMatch.number}`);
return {
number: exactMatch.number,
title: exactMatch.title,
body: exactMatch.body
};
}
}
}
}
} catch (error) {
console.warn(`⚠️ Could not check existing issues for file ${fileName}:`, error);
console.warn(`⚠️ Could not check existing issues for file ${enhancedTitle}:`, error);
}

return null;
}

async closeOldIssueForMigration(issueNumber: number, fileName: string): Promise<void> {
async closeOldIssueForMigration(issueNumber: number, enhancedTitle: string): Promise<void> {
if (!this.token) return;

try {
Expand All @@ -731,7 +830,7 @@ All instances of \`${ruleId}\` violations have been fixed. This issue is now aut
This issue is being closed as we're migrating to a new file-based issue format for better organization.

**Old format:** Rule-based grouping
**New format:** File-based grouping (\`${fileName}\`)
**New format:** File-based grouping with region context (\`${enhancedTitle}\`)

A new issue will be created with the updated format to track the same lint violations.

Expand All @@ -755,7 +854,7 @@ A new issue will be created with the updated format to track the same lint viola
});

if (response.ok) {
console.log(`✅ Closed old format issue #${issueNumber} for migration to file-based format`);
console.log(`✅ Closed old format issue #${issueNumber} for migration to ${enhancedTitle}`);
} else {
console.warn(`⚠️ Failed to close old format issue #${issueNumber}:`, response.statusText);
}
Expand Down