From 2182a4d6579630cfd3f8f46f5a7143389c2c144e Mon Sep 17 00:00:00 2001 From: yuxuanj Date: Wed, 4 Feb 2026 16:33:27 -0800 Subject: [PATCH 1/3] poc: bot scripts --- bin/runLint.js | 92 +++++++++++++++++++++++- bot-poc/bot-scripts.js | 160 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 bot-poc/bot-scripts.js diff --git a/bin/runLint.js b/bin/runLint.js index 7b0c72f..1a8ee56 100644 --- a/bin/runLint.js +++ b/bin/runLint.js @@ -13,6 +13,12 @@ import fs from 'node:fs'; const { log, verbose, logSection, logStep } = await import('./scriptUtils.js'); +// Report collection for linter-report.txt +const reportLines = []; +function addToReport(message) { + reportLines.push(message); +} + // Get the directory where this script is located (adp-devsite-utils repo) const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -43,14 +49,30 @@ const deadLinksOnly = process.argv.includes('--dead-links-only'); const skipDeadLinks = process.argv.includes('--skip-dead-links'); logSection('TEST LINT'); + +// Determine lint mode for report +let lintMode = 'Full Linting (all rules + dead links check)'; if (deadLinksOnly) { logStep('Testing dead links only'); + lintMode = 'Dead Links Only'; } else if (skipDeadLinks) { logStep('Testing remark rules (skipping dead links check)'); + lintMode = 'Remark Rules Only (dead links skipped)'; } else { logStep('Testing remark rules with JavaScript API'); } +// Add report header +addToReport('═══════════════════════════════════════════════════════════════'); +addToReport(' LINTER REPORT'); +addToReport('═══════════════════════════════════════════════════════════════'); +addToReport(''); +addToReport(`Generated: ${new Date().toISOString()}`); +addToReport(`Mode: ${lintMode}`); +addToReport(`Target Directory: ${targetDir}`); +addToReport(''); +addToReport('───────────────────────────────────────────────────────────────'); + // Import the custom linter plugins const remarkLintCheckFrontmatter = await import(path.join(adpDevsiteUtilsDir, 'linters', 'remark-lint-check-frontmatter.js')); const remarkLintNoAngleBrackets = await import(path.join(adpDevsiteUtilsDir, 'linters', 'remark-lint-no-angle-brackets.js')); @@ -158,11 +180,16 @@ if (markdownFiles.length === 0) { } verbose(`Found ${markdownFiles.length} markdown files to test`); +addToReport(''); +addToReport(`Files to process: ${markdownFiles.length}`); +addToReport(''); // Process each file let totalIssues = 0; let filesWithIssues = 0; let hasFatalErrors = false; +let totalErrors = 0; +let totalWarnings = 0; for (const filePath of markdownFiles) { try { @@ -178,19 +205,35 @@ for (const filePath of markdownFiles) { filesWithIssues++; totalIssues += result.messages.length; verbose(`\n${relativePath}:`); + + // Add file header to report + addToReport('───────────────────────────────────────────────────────────────'); + addToReport(`📄 FILE: ${relativePath}`); + addToReport('───────────────────────────────────────────────────────────────'); // Display all messages for this file result.messages.forEach(message => { const severity = message.fatal ? '❌ ERROR' : '⚠️ WARNING'; verbose(` ${severity} ${message}`); - + + // Track error/warning counts if (message.fatal) { hasFatalErrors = true; + totalErrors++; + } else { + totalWarnings++; } + // Add to report with detailed formatting + const location = message.line ? `Line ${message.line}${message.column ? `:${message.column}` : ''}` : 'N/A'; + addToReport(` ${severity}`); + addToReport(` Location: ${location}`); + addToReport(` Message: ${message.message || message}`); if (message.ruleId) { + addToReport(` Rule: ${message.ruleId}`); verbose(` Rule: ${message.ruleId}`); } + addToReport(''); }); } else { verbose(`✅ ${relativePath}: No issues found`); @@ -199,7 +242,17 @@ for (const filePath of markdownFiles) { } catch (error) { log(`❌ Error processing ${filePath}: ${error}`, 'error'); totalIssues++; + totalErrors++; hasFatalErrors = true; + + // Add processing error to report + const relativePath = path.relative(targetDir, filePath); + addToReport('───────────────────────────────────────────────────────────────'); + addToReport(`📄 FILE: ${relativePath}`); + addToReport('───────────────────────────────────────────────────────────────'); + addToReport(` ❌ ERROR`); + addToReport(` Message: Failed to process file - ${error.message || error}`); + addToReport(''); } } @@ -209,6 +262,43 @@ log(` Files processed: ${markdownFiles.length}`); log(` Files with issues: ${filesWithIssues}`); log(` Total issues: ${totalIssues}`); +// Add summary to report +addToReport(''); +addToReport('═══════════════════════════════════════════════════════════════'); +addToReport(' SUMMARY'); +addToReport('═══════════════════════════════════════════════════════════════'); +addToReport(''); +addToReport(` 📁 Files processed: ${markdownFiles.length}`); +addToReport(` 📄 Files with issues: ${filesWithIssues}`); +addToReport(` ❌ Total errors: ${totalErrors}`); +addToReport(` ⚠️ Total warnings: ${totalWarnings}`); +addToReport(` 📋 Total issues: ${totalIssues}`); +addToReport(''); + +let exitStatus; +if (hasFatalErrors) { + addToReport('Result: ❌ FAILED - Fatal errors found'); + exitStatus = 1; +} else if (totalIssues > 0) { + addToReport('Result: ⚠️ PASSED WITH WARNINGS - No fatal errors'); + exitStatus = 0; +} else { + addToReport('Result: ✅ PASSED - All files passed linting successfully!'); + exitStatus = 0; +} + +addToReport(''); +addToReport('═══════════════════════════════════════════════════════════════'); + +// Write report to file +const reportPath = path.join(targetDir, 'linter-report.txt'); +try { + fs.writeFileSync(reportPath, reportLines.join('\n'), 'utf8'); + log(`📝 Linter report written to: ${reportPath}`); +} catch (writeError) { + log(`⚠️ Failed to write linter report: ${writeError.message}`, 'warn'); +} + if (hasFatalErrors) { log('❌ Fatal errors found. Exiting with code 1.', 'error'); process.exit(1); diff --git a/bot-poc/bot-scripts.js b/bot-poc/bot-scripts.js new file mode 100644 index 0000000..cc02004 --- /dev/null +++ b/bot-poc/bot-scripts.js @@ -0,0 +1,160 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * Read the linter report from linter-report.txt + * @param {string} reportPath - Path to the linter report file + * @returns {string} - The content of the report file + */ +function readLinterReport(reportPath) { + if (!fs.existsSync(reportPath)) { + throw new Error(`Linter report not found at: ${reportPath}`); + } + return fs.readFileSync(reportPath, 'utf8'); +} + +/** + * Create a comment on a GitHub Pull Request + * DOCS: https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#create-an-issue-comment + * Note: GitHub treats PR comments as issue comments + * + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {number} prNumber - Pull request number + * @param {string} body - Comment body + * @param {string} githubToken - GitHub authentication token + * @returns {Promise} - The created comment response + */ +async function createPRComment(owner, repo, prNumber, body, githubToken) { + const token = githubToken || process.env.GITHUB_TOKEN; + + if (!token) { + throw new Error('GitHub token is required. Set GITHUB_TOKEN environment variable.'); + } + + try { + const response = await fetch( + `https://api.github.com/repos/${owner}/${repo}/issues/${prNumber}/comments`, + { + method: 'POST', + headers: { + 'Accept': 'application/vnd.github+json', + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'X-GitHub-Api-Version': '2022-11-28' + }, + body: JSON.stringify({ + body: body + }) + } + ); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to create PR comment: ${response.status} - ${errorText}`); + } + + return await response.json(); + } catch (error) { + console.error('Error creating PR comment:', error); + throw error; + } +} + +/** + * Format the linter report as a GitHub markdown comment + * @param {string} reportContent - Raw linter report content + * @returns {string} - Formatted markdown comment + */ +function formatReportAsMarkdown(reportContent) { + return `## 🔍 Linter Report + +
+Click to expand full report + +\`\`\` +${reportContent} +\`\`\` + +
+ +--- +*This comment was automatically generated by the linter bot.*`; +} + +/** + * Main execution function + */ +async function main() { + // Get required environment variables + const prId = process.env.PR_ID; + const githubToken = process.env.GITHUB_TOKEN; + const githubRepository = process.env.GITHUB_REPOSITORY; // format: owner/repo + + // Validate required environment variables + if (!prId) { + console.error('❌ Error: PR_ID environment variable is required'); + process.exit(1); + } + + if (!githubToken) { + console.error('❌ Error: GITHUB_TOKEN environment variable is required'); + process.exit(1); + } + + if (!githubRepository) { + console.error('❌ Error: GITHUB_REPOSITORY environment variable is required'); + process.exit(1); + } + + // Parse owner and repo from GITHUB_REPOSITORY + const [owner, repo] = githubRepository.split('/'); + if (!owner || !repo) { + console.error('❌ Error: GITHUB_REPOSITORY must be in format "owner/repo"'); + process.exit(1); + } + + // Determine report path (default to current working directory) + const reportPath = process.env.LINTER_REPORT_PATH || path.join(process.cwd(), 'linter-report.txt'); + + console.log('📋 Linter Report Bot'); + console.log('─'.repeat(50)); + console.log(`Repository: ${owner}/${repo}`); + console.log(`PR Number: ${prId}`); + console.log(`Report Path: ${reportPath}`); + console.log('─'.repeat(50)); + + try { + // Read the linter report + console.log('📖 Reading linter report...'); + const reportContent = readLinterReport(reportPath); + + // Format as markdown + const markdownComment = formatReportAsMarkdown(reportContent); + + // Post comment to PR + console.log('📝 Posting comment to PR...'); + const result = await createPRComment(owner, repo, parseInt(prId, 10), markdownComment, githubToken); + + console.log('✅ Successfully posted linter report to PR!'); + console.log(` Comment URL: ${result.html_url}`); + + } catch (error) { + console.error(`❌ Failed to post linter report: ${error.message}`); + process.exit(1); + } +} + +// Run main function +main(); + +// Export functions for testing or reuse +export { + readLinterReport, + createPRComment, + formatReportAsMarkdown +}; From dad698307e237cc030b12c577510c0476a5116fe Mon Sep 17 00:00:00 2001 From: yuxuanj Date: Thu, 5 Feb 2026 09:12:12 -0800 Subject: [PATCH 2/3] move bot scripts to bin --- bot-poc/bot-scripts.js => bin/postLinterReport.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename bot-poc/bot-scripts.js => bin/postLinterReport.js (100%) diff --git a/bot-poc/bot-scripts.js b/bin/postLinterReport.js similarity index 100% rename from bot-poc/bot-scripts.js rename to bin/postLinterReport.js From 7f8fd853b90aeefca18c1853790c2ce88de4cd42 Mon Sep 17 00:00:00 2001 From: yuxuanj Date: Tue, 17 Feb 2026 11:40:37 -0800 Subject: [PATCH 3/3] remove reports js --- bin/postLinterReport.js | 160 ---------------------------------------- 1 file changed, 160 deletions(-) delete mode 100644 bin/postLinterReport.js diff --git a/bin/postLinterReport.js b/bin/postLinterReport.js deleted file mode 100644 index cc02004..0000000 --- a/bin/postLinterReport.js +++ /dev/null @@ -1,160 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -/** - * Read the linter report from linter-report.txt - * @param {string} reportPath - Path to the linter report file - * @returns {string} - The content of the report file - */ -function readLinterReport(reportPath) { - if (!fs.existsSync(reportPath)) { - throw new Error(`Linter report not found at: ${reportPath}`); - } - return fs.readFileSync(reportPath, 'utf8'); -} - -/** - * Create a comment on a GitHub Pull Request - * DOCS: https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#create-an-issue-comment - * Note: GitHub treats PR comments as issue comments - * - * @param {string} owner - Repository owner - * @param {string} repo - Repository name - * @param {number} prNumber - Pull request number - * @param {string} body - Comment body - * @param {string} githubToken - GitHub authentication token - * @returns {Promise} - The created comment response - */ -async function createPRComment(owner, repo, prNumber, body, githubToken) { - const token = githubToken || process.env.GITHUB_TOKEN; - - if (!token) { - throw new Error('GitHub token is required. Set GITHUB_TOKEN environment variable.'); - } - - try { - const response = await fetch( - `https://api.github.com/repos/${owner}/${repo}/issues/${prNumber}/comments`, - { - method: 'POST', - headers: { - 'Accept': 'application/vnd.github+json', - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json', - 'X-GitHub-Api-Version': '2022-11-28' - }, - body: JSON.stringify({ - body: body - }) - } - ); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Failed to create PR comment: ${response.status} - ${errorText}`); - } - - return await response.json(); - } catch (error) { - console.error('Error creating PR comment:', error); - throw error; - } -} - -/** - * Format the linter report as a GitHub markdown comment - * @param {string} reportContent - Raw linter report content - * @returns {string} - Formatted markdown comment - */ -function formatReportAsMarkdown(reportContent) { - return `## 🔍 Linter Report - -
-Click to expand full report - -\`\`\` -${reportContent} -\`\`\` - -
- ---- -*This comment was automatically generated by the linter bot.*`; -} - -/** - * Main execution function - */ -async function main() { - // Get required environment variables - const prId = process.env.PR_ID; - const githubToken = process.env.GITHUB_TOKEN; - const githubRepository = process.env.GITHUB_REPOSITORY; // format: owner/repo - - // Validate required environment variables - if (!prId) { - console.error('❌ Error: PR_ID environment variable is required'); - process.exit(1); - } - - if (!githubToken) { - console.error('❌ Error: GITHUB_TOKEN environment variable is required'); - process.exit(1); - } - - if (!githubRepository) { - console.error('❌ Error: GITHUB_REPOSITORY environment variable is required'); - process.exit(1); - } - - // Parse owner and repo from GITHUB_REPOSITORY - const [owner, repo] = githubRepository.split('/'); - if (!owner || !repo) { - console.error('❌ Error: GITHUB_REPOSITORY must be in format "owner/repo"'); - process.exit(1); - } - - // Determine report path (default to current working directory) - const reportPath = process.env.LINTER_REPORT_PATH || path.join(process.cwd(), 'linter-report.txt'); - - console.log('📋 Linter Report Bot'); - console.log('─'.repeat(50)); - console.log(`Repository: ${owner}/${repo}`); - console.log(`PR Number: ${prId}`); - console.log(`Report Path: ${reportPath}`); - console.log('─'.repeat(50)); - - try { - // Read the linter report - console.log('📖 Reading linter report...'); - const reportContent = readLinterReport(reportPath); - - // Format as markdown - const markdownComment = formatReportAsMarkdown(reportContent); - - // Post comment to PR - console.log('📝 Posting comment to PR...'); - const result = await createPRComment(owner, repo, parseInt(prId, 10), markdownComment, githubToken); - - console.log('✅ Successfully posted linter report to PR!'); - console.log(` Comment URL: ${result.html_url}`); - - } catch (error) { - console.error(`❌ Failed to post linter report: ${error.message}`); - process.exit(1); - } -} - -// Run main function -main(); - -// Export functions for testing or reuse -export { - readLinterReport, - createPRComment, - formatReportAsMarkdown -};