nutjs-windows-control
by Cheffromspace
Verified
- MCPControl
- .github
- workflows
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}`);
}