@leverege/build-tools 2.117.0 → 2.119.0-pedro.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/branch-protect-it.mjs +178 -0
- package/src/dashboard/json/pitcrew/pubsub-repeater.json +1118 -0
- package/src/github-actions-it.mjs +3 -0
- package/src/helm-charts/authz-server/values-local.yaml +0 -3
- package/src/helm-charts/elasticsearch8/elasticsearch-local.yaml +6 -6
- package/src/helm-charts/message-processor/values-local.yaml +0 -3
- package/src/repo-panopticon.mjs +12 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leverege/build-tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.119.0-pedro.1",
|
|
4
4
|
"description": "A collection of build / support tools for Leverege developers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"test-all": "mocha -t 60000 --exit 'test/**/**.mjs'"
|
|
20
20
|
},
|
|
21
21
|
"bin": {
|
|
22
|
+
"branch-protect-it": "src/branch-protect-it.mjs",
|
|
22
23
|
"build-cnpg-image": "src/build-cnpg-image.mjs",
|
|
23
24
|
"build-tools": "src/build-tools.mjs",
|
|
24
25
|
"chart-compass": "src/chart-compass.mjs",
|
|
@@ -103,7 +104,7 @@
|
|
|
103
104
|
"googleapis": "^176.0.0",
|
|
104
105
|
"handlebars": "^4.7.9",
|
|
105
106
|
"ignore": "^7.0.6",
|
|
106
|
-
"inquirer": "^14.
|
|
107
|
+
"inquirer": "^14.2.0",
|
|
107
108
|
"js-yaml": "^4.2.0",
|
|
108
109
|
"jsdoc": "^4.0.5",
|
|
109
110
|
"ms": "^2.1.3",
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { $ } from 'zx'
|
|
3
|
+
import { program } from 'commander'
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
|
|
6
|
+
import { enableDebug, errorExit, log, proceed } from './Utils.mjs'
|
|
7
|
+
|
|
8
|
+
$.verbose = false
|
|
9
|
+
|
|
10
|
+
const DEFAULT_ORG = 'Leverege'
|
|
11
|
+
const REQUIRED_CHECK_PREFIX = 'node-ci'
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name( 'branch-protect-it' )
|
|
15
|
+
.description( chalk.green( 'Applies the standard branch-protection + merge-method bundle to a repo' ) )
|
|
16
|
+
.argument( '<repo>', 'Repository name (without org prefix)' )
|
|
17
|
+
.option( '--org <org>', 'GitHub org', DEFAULT_ORG )
|
|
18
|
+
.option( '--branch <branch>', "Branch to protect (defaults to the repo's actual default branch)" )
|
|
19
|
+
.option( '--require-approvals <n>', 'Required approving review count', '0' )
|
|
20
|
+
.option( '--dry-run', 'Show what would be applied without making changes' )
|
|
21
|
+
.option( '--check', 'Read-only: exits 0 (matches the standard bundle) or 1 (does not) — ' +
|
|
22
|
+
'no prompt, no writes, no output at all by default. For scripting/dashboards (e.g. ' +
|
|
23
|
+
'repo-panopticon) that only care about the exit code. Pair with --check-verbose for output.' )
|
|
24
|
+
.option( '--check-verbose', 'With --check, also print which specific settings are missing/wrong ' +
|
|
25
|
+
'(or a success line). Has no effect without --check.' )
|
|
26
|
+
.option( '--debug', 'Enable debug logging' )
|
|
27
|
+
.addHelpText( 'after', `
|
|
28
|
+
${chalk.yellow( 'What this applies:' )}
|
|
29
|
+
- Require a pull request before merging (0+ approvals, configurable)
|
|
30
|
+
- Require the repo's node-ci status check to pass, and the branch to be up to date
|
|
31
|
+
- Block force pushes and branch deletion
|
|
32
|
+
- Admins can still bypass (enforce_admins: false) for emergencies
|
|
33
|
+
- Repo-level merge method restricted to squash-only
|
|
34
|
+
- Auto-delete head branches on merge (delete_branch_on_merge)
|
|
35
|
+
- Suggest updating out-of-date PR branches with one click (allow_update_branch)
|
|
36
|
+
|
|
37
|
+
${chalk.yellow( 'Scope:' )}
|
|
38
|
+
Only supports repos on the ${chalk.green( 'node-ci' )} reusable workflow for now — it looks up
|
|
39
|
+
the real check-run name from the target branch's latest commit (never guesses one) and
|
|
40
|
+
errors out if nothing starting with "node-ci" is found there.
|
|
41
|
+
|
|
42
|
+
${chalk.yellow( 'Examples:' )}
|
|
43
|
+
${chalk.green( 'branch-protect-it secrets' )}
|
|
44
|
+
${chalk.green( 'branch-protect-it secrets --dry-run' )}
|
|
45
|
+
${chalk.green( 'branch-protect-it secrets --require-approvals 1' )}
|
|
46
|
+
${chalk.green( 'branch-protect-it secrets --check && echo protected' )}
|
|
47
|
+
${chalk.green( 'branch-protect-it secrets --check --check-verbose' )}
|
|
48
|
+
` )
|
|
49
|
+
.parse()
|
|
50
|
+
|
|
51
|
+
const [ repo ] = program.args
|
|
52
|
+
const opts = program.opts()
|
|
53
|
+
if ( opts.debug ) { enableDebug(); $.verbose = true }
|
|
54
|
+
|
|
55
|
+
const fullRepo = `${opts.org}/${repo}`
|
|
56
|
+
// --check is silent by default (exit code only) — only chatty with --check-verbose.
|
|
57
|
+
// Every other mode (apply/dry-run) always logs normally.
|
|
58
|
+
const quietCheck = opts.check && !opts.checkVerbose
|
|
59
|
+
const checkLog = ( ...args ) => { if ( !quietCheck ) { log( ...args ) } }
|
|
60
|
+
|
|
61
|
+
// ---------- Resolve branch ----------
|
|
62
|
+
|
|
63
|
+
let branch = opts.branch
|
|
64
|
+
if ( !branch ) {
|
|
65
|
+
const { stdout } = await $`gh api -X GET repos/${fullRepo} -q .default_branch`
|
|
66
|
+
branch = stdout.trim()
|
|
67
|
+
}
|
|
68
|
+
checkLog( chalk.blue( `Target: ${fullRepo}@${branch}` ) )
|
|
69
|
+
|
|
70
|
+
// ---------- --check: read-only status, exit 0/1, no writes, no prompt ----------
|
|
71
|
+
|
|
72
|
+
if ( opts.check ) {
|
|
73
|
+
const reasons = []
|
|
74
|
+
|
|
75
|
+
let protection = null
|
|
76
|
+
try {
|
|
77
|
+
// .quiet() suppresses gh's own stderr (e.g. "Branch not protected") — that's an
|
|
78
|
+
// expected, handled outcome here, not something that should leak into --check's output
|
|
79
|
+
const { stdout } = await $`gh api -X GET repos/${fullRepo}/branches/${branch}/protection`.quiet()
|
|
80
|
+
protection = JSON.parse( stdout )
|
|
81
|
+
} catch {
|
|
82
|
+
reasons.push( 'no branch protection configured at all' )
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if ( protection ) {
|
|
86
|
+
if ( !protection.required_pull_request_reviews ) { reasons.push( 'PR not required' ) }
|
|
87
|
+
if ( !protection.required_status_checks?.strict ) { reasons.push( 'status checks not required / not strict' ) }
|
|
88
|
+
if ( !protection.required_status_checks?.contexts?.some( c => c.startsWith( REQUIRED_CHECK_PREFIX ) ) ) {
|
|
89
|
+
reasons.push( `no required check starting with "${REQUIRED_CHECK_PREFIX}"` )
|
|
90
|
+
}
|
|
91
|
+
if ( protection.allow_force_pushes?.enabled !== false ) { reasons.push( 'force pushes not blocked' ) }
|
|
92
|
+
if ( protection.allow_deletions?.enabled !== false ) { reasons.push( 'branch deletion not blocked' ) }
|
|
93
|
+
if ( protection.enforce_admins?.enabled !== false ) { reasons.push( 'enforce_admins is not false (expected: admins can bypass)' ) }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let repoSettings = null
|
|
97
|
+
try {
|
|
98
|
+
const { stdout } = await $`gh api -X GET repos/${fullRepo} -q '{allow_squash_merge, allow_merge_commit, allow_rebase_merge, delete_branch_on_merge, allow_update_branch}'`
|
|
99
|
+
repoSettings = JSON.parse( stdout )
|
|
100
|
+
} catch ( error ) {
|
|
101
|
+
reasons.push( `could not read repo merge settings: ${error.stderr ?? error.message ?? error}` )
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if ( repoSettings ) {
|
|
105
|
+
if ( repoSettings.allow_squash_merge !== true ) { reasons.push( 'squash merge not enabled' ) }
|
|
106
|
+
if ( repoSettings.allow_merge_commit !== false ) { reasons.push( 'merge commits not disabled' ) }
|
|
107
|
+
if ( repoSettings.allow_rebase_merge !== false ) { reasons.push( 'rebase merge not disabled' ) }
|
|
108
|
+
if ( repoSettings.delete_branch_on_merge !== true ) { reasons.push( 'delete_branch_on_merge not enabled' ) }
|
|
109
|
+
if ( repoSettings.allow_update_branch !== true ) { reasons.push( 'allow_update_branch not enabled' ) }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if ( reasons.length === 0 ) {
|
|
113
|
+
checkLog( chalk.green( `✔ ${fullRepo}@${branch} matches the standard branch-protection bundle` ) )
|
|
114
|
+
process.exit( 0 )
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
checkLog( chalk.red( `✘ ${fullRepo}@${branch} does not match the standard bundle:` ) )
|
|
118
|
+
reasons.forEach( r => checkLog( chalk.red( ` - ${r}` ) ) )
|
|
119
|
+
process.exit( 1 )
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------- Find the real node-ci check name — never guess it ----------
|
|
123
|
+
|
|
124
|
+
let checkName
|
|
125
|
+
try {
|
|
126
|
+
const { stdout : shaRaw } = await $`gh api -X GET repos/${fullRepo}/commits/${branch} -q .sha`
|
|
127
|
+
const sha = shaRaw.trim()
|
|
128
|
+
const { stdout : checksRaw } = await $`gh api -X GET repos/${fullRepo}/commits/${sha}/check-runs -q '.check_runs[].name'`
|
|
129
|
+
const checks = checksRaw.trim().split( '\n' ).filter( Boolean )
|
|
130
|
+
checkName = checks.find( c => c.startsWith( REQUIRED_CHECK_PREFIX ) )
|
|
131
|
+
} catch ( error ) {
|
|
132
|
+
errorExit( `Failed to look up check runs: ${error.stderr ?? error.message ?? error}` )
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if ( !checkName ) {
|
|
136
|
+
errorExit(
|
|
137
|
+
`No check run starting with "${REQUIRED_CHECK_PREFIX}" found on ${fullRepo}@${branch}'s latest commit.\n` +
|
|
138
|
+
' Has the node-ci workflow actually run on this branch yet? Push something first, ' +
|
|
139
|
+
'or pass --branch if development is not the right target.'
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
log( chalk.green( `Found check: ${checkName}` ) )
|
|
143
|
+
|
|
144
|
+
// ---------- Build the payload ----------
|
|
145
|
+
|
|
146
|
+
const protectionPayload = {
|
|
147
|
+
required_status_checks : { strict : true, contexts : [ checkName ] },
|
|
148
|
+
enforce_admins : false,
|
|
149
|
+
required_pull_request_reviews : { required_approving_review_count : Number( opts.requireApprovals ) },
|
|
150
|
+
restrictions : null,
|
|
151
|
+
allow_force_pushes : false,
|
|
152
|
+
allow_deletions : false,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
log( chalk.yellow( '\nWill apply branch protection:' ) )
|
|
156
|
+
log( JSON.stringify( protectionPayload, null, 2 ) )
|
|
157
|
+
log( chalk.yellow( 'Will also set merge method to squash-only ' +
|
|
158
|
+
'(allow_squash_merge=true, allow_merge_commit=false, allow_rebase_merge=false), ' +
|
|
159
|
+
'enable auto-delete of head branches on merge (delete_branch_on_merge=true), ' +
|
|
160
|
+
'and enable the "Update branch" suggestion button on out-of-date PRs (allow_update_branch=true)' ) )
|
|
161
|
+
|
|
162
|
+
if ( opts.dryRun ) {
|
|
163
|
+
log( chalk.green( '\n[dry-run] No changes made.' ) )
|
|
164
|
+
process.exit( 0 )
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
await proceed( `\nApply this to ${fullRepo}@${branch}?` )
|
|
168
|
+
|
|
169
|
+
// ---------- Apply ----------
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await $( { input : JSON.stringify( protectionPayload ) } )`gh api --method PUT repos/${fullRepo}/branches/${branch}/protection --input -`
|
|
173
|
+
await $`gh api --method PATCH repos/${fullRepo} -F allow_squash_merge=true -F allow_merge_commit=false -F allow_rebase_merge=false -F delete_branch_on_merge=true -F allow_update_branch=true`
|
|
174
|
+
} catch ( error ) {
|
|
175
|
+
errorExit( `Failed to apply settings: ${error.stderr ?? error.message ?? error}` )
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
log( chalk.green( `\n✔ Branch protection applied to ${fullRepo}@${branch}` ) )
|