mirror of
https://github.com/dtzp555-max/clawkeeper.git
synced 2026-07-19 09:41:42 +00:00
feat: add GitHub repo rule detector script
This commit is contained in:
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* repo-rule-detector.mjs
|
||||
*
|
||||
* Detect GitHub repository rules that commonly affect dev flow:
|
||||
* - require PRs for main
|
||||
* - required status checks
|
||||
* - required reviews
|
||||
* - admin enforcement
|
||||
* - allowed merge methods (merge commit / squash / rebase)
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/repo-rule-detector.mjs owner/repo
|
||||
* node scripts/repo-rule-detector.mjs owner/repo --branch main
|
||||
*
|
||||
* Requires:
|
||||
* - gh auth
|
||||
*/
|
||||
|
||||
import { execFileSync } from 'node:child_process';
|
||||
|
||||
function sh(args) {
|
||||
return execFileSync('gh', args, { encoding: 'utf8' }).trim();
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const out = { repo: null, branch: 'main' };
|
||||
const rest = [];
|
||||
for (let i = 2; i < argv.length; i++) {
|
||||
const a = argv[i];
|
||||
if (a === '--branch') { out.branch = argv[++i]; continue; }
|
||||
rest.push(a);
|
||||
}
|
||||
if (rest.length < 1) throw new Error('Missing repo argument: owner/repo');
|
||||
out.repo = rest[0];
|
||||
return out;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const { repo, branch } = parseArgs(process.argv);
|
||||
|
||||
const repoInfo = JSON.parse(sh(['api', `repos/${repo}`]));
|
||||
|
||||
// Branch protection (classic)
|
||||
let bp = null;
|
||||
try {
|
||||
bp = JSON.parse(sh(['api', `repos/${repo}/branches/${branch}/protection` ]));
|
||||
} catch {
|
||||
bp = null;
|
||||
}
|
||||
|
||||
// Repo rulesets (new)
|
||||
let rulesets = [];
|
||||
try {
|
||||
rulesets = JSON.parse(sh(['api', `repos/${repo}/rulesets`, '--paginate']));
|
||||
} catch {
|
||||
rulesets = [];
|
||||
}
|
||||
|
||||
const relevantRulesets = rulesets
|
||||
.filter(rs => rs?.enforcement && rs.enforcement !== 'disabled')
|
||||
.filter(rs => {
|
||||
const targets = rs?.conditions?.ref_name?.include ?? [];
|
||||
return targets.length === 0 || targets.includes('~DEFAULT_BRANCH') || targets.includes(branch) || targets.includes(`refs/heads/${branch}`);
|
||||
})
|
||||
.map(rs => ({
|
||||
id: rs.id,
|
||||
name: rs.name,
|
||||
enforcement: rs.enforcement,
|
||||
target: rs?.conditions?.ref_name ?? null
|
||||
}));
|
||||
|
||||
const allowedMergeMethods = {
|
||||
mergeCommit: !!repoInfo.allow_merge_commit,
|
||||
squash: !!repoInfo.allow_squash_merge,
|
||||
rebase: !!repoInfo.allow_rebase_merge
|
||||
};
|
||||
|
||||
const out = {
|
||||
repo: repoInfo.full_name,
|
||||
url: repoInfo.html_url,
|
||||
defaultBranch: repoInfo.default_branch,
|
||||
branch,
|
||||
allowedMergeMethods,
|
||||
branchProtection: bp ? {
|
||||
enabled: true,
|
||||
requiredStatusChecks: bp.required_status_checks ? {
|
||||
strict: bp.required_status_checks.strict,
|
||||
contexts: bp.required_status_checks.contexts,
|
||||
checks: bp.required_status_checks.checks
|
||||
} : null,
|
||||
requiredPullRequestReviews: bp.required_pull_request_reviews ? {
|
||||
requiredApprovingReviewCount: bp.required_pull_request_reviews.required_approving_review_count,
|
||||
requireCodeOwnerReviews: bp.required_pull_request_reviews.require_code_owner_reviews,
|
||||
dismissStaleReviews: bp.required_pull_request_reviews.dismiss_stale_reviews
|
||||
} : null,
|
||||
enforceAdmins: bp.enforce_admins?.enabled ?? null,
|
||||
restrictions: bp.restrictions ?? null
|
||||
} : { enabled: false },
|
||||
rulesets: relevantRulesets
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(out, null, 2));
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user