mirror of
https://github.com/dtzp555-max/clawkeeper.git
synced 2026-07-19 09:41:42 +00:00
repo-rule-detector: summarize rulesets PR parameters
This commit is contained in:
@@ -67,6 +67,71 @@ clawkeeper restore-apply ~/openclaw-backup.tgz
|
||||
clawkeeper restore-apply ~/openclaw-backup.tgz --restart-gateway
|
||||
```
|
||||
|
||||
## Repo rule detector (script)
|
||||
If you need to quickly see what GitHub repo rules might block merges (repo rulesets + classic branch protection), run:
|
||||
|
||||
```bash
|
||||
node scripts/repo-rule-detector.mjs dtzp555-max/ocm --branch main
|
||||
```
|
||||
|
||||
Example output (dtzp555-max/ocm):
|
||||
|
||||
```json
|
||||
{
|
||||
"repo": "dtzp555-max/ocm",
|
||||
"url": "https://github.com/dtzp555-max/ocm",
|
||||
"defaultBranch": "main",
|
||||
"branch": "main",
|
||||
"allowedMergeMethods": {
|
||||
"mergeCommit": true,
|
||||
"squash": true,
|
||||
"rebase": true
|
||||
},
|
||||
"rulesetsRulePresence": {
|
||||
"required_linear_history": true,
|
||||
"non_fast_forward": true,
|
||||
"deletion": true
|
||||
},
|
||||
"branchProtection": {
|
||||
"enabled": false
|
||||
},
|
||||
"rulesets": [
|
||||
{
|
||||
"id": 13362557,
|
||||
"name": "ocm",
|
||||
"enforcement": "active",
|
||||
"target": null,
|
||||
"rules": {
|
||||
"pull_request": {
|
||||
"present": true,
|
||||
"parameters": {
|
||||
"required_review_thread_resolution": true,
|
||||
"allowed_merge_methods": [
|
||||
"merge",
|
||||
"squash",
|
||||
"rebase"
|
||||
],
|
||||
"required_approving_review_count": 0,
|
||||
"require_code_owner_review": false,
|
||||
"dismiss_stale_reviews_on_push": false,
|
||||
"require_last_push_approval": false
|
||||
}
|
||||
},
|
||||
"required_linear_history": {
|
||||
"present": true
|
||||
},
|
||||
"non_fast_forward": {
|
||||
"present": true
|
||||
},
|
||||
"deletion": {
|
||||
"present": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Safety notes
|
||||
This tool edits `~/.openclaw/openclaw.json`.
|
||||
- Always makes a timestamped backup before modifying config.
|
||||
|
||||
@@ -20,7 +20,18 @@
|
||||
import { execFileSync } from 'node:child_process';
|
||||
|
||||
function sh(args) {
|
||||
return execFileSync('gh', args, { encoding: 'utf8' }).trim();
|
||||
// Keep stdout machine-readable: never let gh chatter leak into our output.
|
||||
return execFileSync('gh', args, {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
}).trim();
|
||||
}
|
||||
|
||||
function pick(obj, keys) {
|
||||
if (!obj) return null;
|
||||
const out = {};
|
||||
for (const k of keys) out[k] = obj?.[k] ?? null;
|
||||
return out;
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
@@ -57,18 +68,58 @@ function main() {
|
||||
rulesets = [];
|
||||
}
|
||||
|
||||
const PULL_REQUEST_PARAM_KEYS = [
|
||||
'required_review_thread_resolution',
|
||||
'allowed_merge_methods',
|
||||
'required_approving_review_count',
|
||||
'require_code_owner_review',
|
||||
'dismiss_stale_reviews_on_push',
|
||||
'require_last_push_approval'
|
||||
];
|
||||
|
||||
function fetchRulesetDetails(rulesetId) {
|
||||
try {
|
||||
return JSON.parse(sh(['api', `repos/${repo}/rulesets/${rulesetId}`]));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function hasRuleType(rules, type) {
|
||||
return Array.isArray(rules) && rules.some(r => r?.type === type);
|
||||
}
|
||||
|
||||
function summarizeRulesetRules(rules) {
|
||||
const prRule = Array.isArray(rules) ? rules.find(r => r?.type === 'pull_request') : null;
|
||||
const prParams = prRule?.parameters ? pick(prRule.parameters, PULL_REQUEST_PARAM_KEYS) : null;
|
||||
|
||||
return {
|
||||
pull_request: prParams
|
||||
? { present: true, parameters: prParams }
|
||||
: { present: false, parameters: null },
|
||||
required_linear_history: { present: hasRuleType(rules, 'required_linear_history') },
|
||||
non_fast_forward: { present: hasRuleType(rules, 'non_fast_forward') },
|
||||
deletion: { present: hasRuleType(rules, 'deletion') }
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
}));
|
||||
.map(rs => {
|
||||
const details = fetchRulesetDetails(rs.id);
|
||||
const rulesSummary = summarizeRulesetRules(details?.rules ?? []);
|
||||
return {
|
||||
id: rs.id,
|
||||
name: rs.name,
|
||||
enforcement: rs.enforcement,
|
||||
target: rs?.conditions?.ref_name ?? null,
|
||||
rules: rulesSummary
|
||||
};
|
||||
});
|
||||
|
||||
const allowedMergeMethods = {
|
||||
mergeCommit: !!repoInfo.allow_merge_commit,
|
||||
@@ -76,12 +127,19 @@ function main() {
|
||||
rebase: !!repoInfo.allow_rebase_merge
|
||||
};
|
||||
|
||||
const rulesetsRulePresence = {
|
||||
required_linear_history: relevantRulesets.some(rs => rs?.rules?.required_linear_history?.present),
|
||||
non_fast_forward: relevantRulesets.some(rs => rs?.rules?.non_fast_forward?.present),
|
||||
deletion: relevantRulesets.some(rs => rs?.rules?.deletion?.present)
|
||||
};
|
||||
|
||||
const out = {
|
||||
repo: repoInfo.full_name,
|
||||
url: repoInfo.html_url,
|
||||
defaultBranch: repoInfo.default_branch,
|
||||
branch,
|
||||
allowedMergeMethods,
|
||||
rulesetsRulePresence,
|
||||
branchProtection: bp ? {
|
||||
enabled: true,
|
||||
requiredStatusChecks: bp.required_status_checks ? {
|
||||
|
||||
Reference in New Issue
Block a user