Files
awesome-opencode/scripts/check-pr.js
T
John SmithandClaude Opus 5.5 ef6357d8d1 feat(ci): required validate check with V2 probe, and pull_request_target triage
validate-pr.yml now runs on every PR (README-only PRs previously got no feedback), drops tj-actions/changed-files, runs unit tests, scripts/check-pr.js and a README/registry generation dry run, and supports workflow_dispatch so bot branches get the check. New plugin entries that are V1-only are blocked; declared compat must match what is detected. triage.yml labels PRs (ready, needs-v2, needs-metadata, needs-human, category) and keeps one results comment, using base-branch code only and reading PR files as data. The README workflow uses full history and Node 24.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-17 13:00:22 -04:00

82 lines
3.2 KiB
JavaScript

#!/usr/bin/env node
/**
* The required "validate" check for pull requests.
* Usage: node scripts/check-pr.js --base <git-ref> [--no-probe]
*
* Compares HEAD against the merge base with <git-ref>, analyzes the changed files, writes a report to
* $GITHUB_STEP_SUMMARY (when set) and exits 1 on any blocking problem.
*/
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const { loadCatalog } = require('./utils/catalog');
const { DATA_FILE_RE, analyzePullRequest, formatReport } = require('./utils/pr-checks');
const ROOT = path.join(__dirname, '..');
const git = (...args) => execFileSync('git', args, { cwd: ROOT, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
function parseArgs(argv) {
const baseIndex = argv.indexOf('--base');
return {
base: baseIndex !== -1 ? argv[baseIndex + 1] : 'origin/main',
probe: !argv.includes('--no-probe')
};
}
const STATUS = { A: 'added', M: 'modified', D: 'removed', R: 'renamed', C: 'added', T: 'modified' };
function changedFiles(mergeBase) {
const changes = [];
for (const line of git('diff', '--name-status', '-M', `${mergeBase}...HEAD`).split('\n').filter(Boolean)) {
const [code, first, second] = line.split('\t');
const status = STATUS[code[0]] || 'modified';
if (status === 'renamed') {
changes.push({ path: first, status: 'removed' });
changes.push({ path: second, status: 'added' });
} else {
changes.push({ path: first, status });
}
}
return changes;
}
async function main() {
const { base, probe } = parseArgs(process.argv.slice(2));
const mergeBase = git('merge-base', base, 'HEAD').trim();
const changes = changedFiles(mergeBase);
for (const change of changes) {
if (!DATA_FILE_RE.test(change.path) || change.status === 'removed') continue;
change.content = fs.readFileSync(path.join(ROOT, change.path), 'utf8');
if (change.status === 'modified') {
try { change.baseContent = git('show', `${mergeBase}:${change.path}`); } catch { /* new in base range */ }
}
}
// Base catalog = current checkout minus/plus this PR's data changes is what analyzePullRequest rebuilds,
// so start from the full head catalog with the changed files removed.
const changedPaths = new Set(changes.map(c => c.path));
const { entries, errors: loadErrors } = loadCatalog();
const baseEntries = entries.filter(e => !changedPaths.has(e.file));
const result = await analyzePullRequest({ changes, baseEntries, probe });
// Unparseable files anywhere in the catalog break README generation, so they always block
for (const err of loadErrors) {
if (!result.errors.some(e => e.file === err.file)) result.errors.push({ ...err, kind: 'metadata' });
}
const report = formatReport(result);
console.log(report);
if (process.env.GITHUB_STEP_SUMMARY) fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, report + '\n');
for (const e of result.errors) console.log(`::error file=${e.file}::${e.message.replace(/\n/g, ' ')}`);
for (const w of result.warnings) console.log(`::warning file=${w.file}::${w.message.replace(/\n/g, ' ')}`);
process.exit(result.errors.length ? 1 : 0);
}
main().catch(err => {
console.error(err);
process.exit(1);
});