nutjs-windows-control

by Cheffromspace
Verified
name: CI on: pull_request: branches: [ master ] push: branches: [ master ] jobs: build: runs-on: windows-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install cmake-js globally run: npm install -g cmake-js - name: Install dependencies run: npm ci - name: Security audit run: npm audit --audit-level=high - name: Check for known vulnerabilities run: npx audit-ci --high - name: Build with libnut-core run: node scripts/build.js - name: Run ESLint run: npm run lint - name: Run tests run: npm test - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} pr-review: runs-on: ubuntu-latest needs: build if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Send PR data to webhook for code review uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | console.log('Processing PR #' + context.issue.number + ' in ' + context.repo.owner + '/' + context.repo.repo); try { // Get PR details const pr = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number }); // Get PR files const files = await github.rest.pulls.listFiles({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number }); console.log('Files changed:', files.data.length); // Setup auth const username = '${{ secrets.WEBHOOK_USERNAME }}'; const password = '${{ secrets.WEBHOOK_AUTH_TOKEN }}'; const webhookUrl = '${{ vars.WEBHOOK_URL }}'; // Validate auth credentials if (!username || !password) { throw new Error('Webhook authentication credentials not properly configured'); } // Validate webhook URL if (!webhookUrl || !webhookUrl.trim()) { throw new Error('WEBHOOK_URL is not configured'); } const url = new URL(webhookUrl); // Ensure HTTPS is used for security if (url.protocol !== 'https:') { throw new Error('WEBHOOK_URL must use HTTPS protocol for security'); } // TEMPORARY: Using Basic Auth until token-based auth is implemented (see issue #37) // Note: This is only base64 encoded, not encrypted, but is protected by HTTPS const auth = Buffer.from(`${username}:${password}`).toString('base64'); // Get PR comments const comments = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number }); // Get PR review comments const reviewComments = await github.rest.pulls.listReviewComments({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number }); // Import PR webhook utilities const fs = require('fs'); const path = require('path'); // Define the path to the utils file const utilsPath = path.join(process.env.GITHUB_WORKSPACE, '.github', 'pr-webhook-utils.cjs'); console.log(`Loading PR webhook utilities from: ${utilsPath}`); // Load the utilities from the external file const prDataUtils = require(utilsPath); // Build PR data payload const prData = { id: pr.data.id, number: pr.data.number, title: prDataUtils.sanitizeText(pr.data.title), body: prDataUtils.sanitizeText(pr.data.body), state: pr.data.state, created_at: pr.data.created_at, updated_at: pr.data.updated_at, repository: { name: context.repo.repo, owner: context.repo.owner }, head: { ref: pr.data.head.ref, sha: pr.data.head.sha }, base: { ref: pr.data.base.ref, sha: pr.data.base.sha }, user: { login: pr.data.user.login, id: pr.data.user.id }, // Filter sensitive files and limit payload size changed_files: files.data .filter(file => prDataUtils.shouldIncludeFile(file.filename)) .slice(0, 100) // Limit to 100 files max .map(file => ({ filename: file.filename, status: file.status, additions: file.additions, deletions: file.deletions, changes: file.changes, patch: prDataUtils.limitPatch(file.patch) })), // Sanitize comments comments: comments.data .slice(0, 100) // Limit to 100 comments max .map(comment => ({ id: comment.id, body: prDataUtils.sanitizeText(comment.body), user: comment.user.login, created_at: comment.created_at })), // Sanitize review comments review_comments: reviewComments.data .slice(0, 100) // Limit to 100 review comments max .map(comment => ({ id: comment.id, body: prDataUtils.sanitizeText(comment.body), user: comment.user.login, path: comment.path, position: comment.position, created_at: comment.created_at })) }; console.log('Sending PR data to webhook...'); // Calculate payload size for logging const payloadSize = JSON.stringify(prData).length; console.log(`Payload size: ${(payloadSize / 1024).toFixed(2)} KB`); // Fail if payload is too large (>5MB) const maxPayloadSize = 5 * 1024 * 1024; if (payloadSize > maxPayloadSize) { throw new Error(`Payload size (${payloadSize} bytes) exceeds maximum allowed size (${maxPayloadSize} bytes)`); } // Use https request const https = require('https'); // Properly stringify and send the data using safe stringify utility const stringifyResult = prDataUtils.safeStringify(prData); if (!stringifyResult.success) { console.error(`JSON stringify error: ${stringifyResult.error}`); // Use the simplified data creator utility const simplifiedData = prDataUtils.createSimplifiedPrData(pr, context); // Try to stringify the simplified data const simplifiedResult = prDataUtils.safeStringify(simplifiedData); if (!simplifiedResult.success) { // Last resort - send minimal JSON console.error(`Even simplified data failed: ${simplifiedResult.error}`); stringifyResult.data = JSON.stringify({ error: "Failed to process PR data", pr_number: context.issue.number }); } else { console.log('Using simplified PR data instead'); stringifyResult.data = simplifiedResult.data; } } else { console.log('JSON data prepared successfully'); } // Log payload size instead of full content for security console.log(`Payload prepared successfully: ${(stringifyResult.data.length / 1024).toFixed(2)} KB`); const options = { hostname: url.hostname, port: url.port || 443, path: url.pathname, method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Basic ${auth}`, 'Content-Length': Buffer.byteLength(stringifyResult.data) }, timeout: 10000 // 10 second timeout }; // Make the request const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { if (res.statusCode >= 200 && res.statusCode < 300) { console.log(`Successfully sent PR data to webhook (Status: ${res.statusCode})`); } else { const errorMsg = `Failed to send PR data to webhook: Status ${res.statusCode}`; console.error(errorMsg); console.error(`Response: ${data}`); // Fail the job if the webhook returns an error core.setFailed(errorMsg); } }); }); req.on('error', (error) => { const errorMsg = `Network error when sending to webhook: ${error.message}`; console.error(errorMsg); core.setFailed(errorMsg); }); req.on('timeout', () => { req.destroy(); const errorMsg = 'Request to webhook timed out after 10 seconds'; console.error(errorMsg); core.setFailed(errorMsg); }); req.write(stringifyResult.data); req.end(); } catch (error) { console.error(`Failed to process PR data: ${error.message}`); core.setFailed(`PR review webhook error: ${error.message}`); }