From d02b48ae0142f25ed7eb767a63bd94f0c400e920 Mon Sep 17 00:00:00 2001 From: Vladimir Klimontovich Date: Mon, 11 May 2026 14:30:14 -0400 Subject: [PATCH 1/3] chore: add branch ruleset template + apply script; release on every main push --- .github/workflows/release.yml | 2 + rulesets/template.json | 41 +++++++++++ scripts/apply-rulesets.sh | 126 ++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 rulesets/template.json create mode 100644 scripts/apply-rulesets.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 033cd0b..b0cb552 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,8 @@ name: Release on: + push: + branches: [main] workflow_dispatch: jobs: diff --git a/rulesets/template.json b/rulesets/template.json new file mode 100644 index 0000000..a0e517d --- /dev/null +++ b/rulesets/template.json @@ -0,0 +1,41 @@ +{ + "name": "default-branch-protection", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["~DEFAULT_BRANCH"], + "exclude": [] + } + }, + "bypass_actors": [ + { + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "always" + } + ], + "rules": [ + { "type": "deletion" }, + { "type": "non_fast_forward" }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": false, + "allowed_merge_methods": ["rebase"] + } + }, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": false, + "do_not_enforce_on_create": false, + "required_status_checks": [] + } + } + ] +} diff --git a/scripts/apply-rulesets.sh b/scripts/apply-rulesets.sh new file mode 100644 index 0000000..3ebec07 --- /dev/null +++ b/scripts/apply-rulesets.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# Apply branch rulesets + repo settings across jitsucom repos. +# +# Dry by default. Pass --apply to write. +# +# Required: gh auth status as a user with Admin on each target repo. +# Idempotent: deletes existing ruleset named "default-branch-protection" before recreating. +# +# Usage: +# scripts/apply-rulesets.sh # dry, all repos +# scripts/apply-rulesets.sh --apply # write, all repos +# scripts/apply-rulesets.sh --apply jitsu # write, single repo + +set -euo pipefail + +ORG=jitsucom +RULESET_NAME=default-branch-protection +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +TEMPLATE="$SCRIPT_DIR/../rulesets/template.json" + +# Format: repo:policy:pipe-separated-check-contexts +# Policy: flexible | pr-required | settings-only +MATRIX=( + "jitsu:flexible:โœจ Lint & Test|๐Ÿงช Bulker Test|ai-review / ai-review" + "jitsu-cloud-infra:flexible:ai-review / ai-review" + "github-workflows:flexible:ai-review" + "jitsu-bi:flexible:" + "mongobetween:pr-required:tests|lint|salus" + "websites:settings-only:" +) + +DRY=1 +TARGET="" +for arg in "$@"; do + case "$arg" in + --apply) DRY=0 ;; + --help|-h) + sed -n '2,/^set -e/p' "$0" | sed 's/^# \{0,1\}//' | sed '$d' + exit 0 + ;; + *) TARGET="$arg" ;; + esac +done + +run() { + if [ "$DRY" = "1" ]; then + printf ' [dry] '; printf '%q ' "$@"; echo + else + "$@" + fi +} + +apply_repo_settings() { + local repo=$1 + echo "โ†’ $repo: repo settings (auto-merge on, rebase only, delete branch on merge)" + run gh api -X PATCH "/repos/$ORG/$repo" \ + -F allow_auto_merge=true \ + -F allow_rebase_merge=true \ + -F allow_squash_merge=false \ + -F allow_merge_commit=false \ + -F delete_branch_on_merge=true +} + +apply_ruleset() { + local repo=$1 policy=$2 checks_csv=$3 + echo "โ†’ $repo: ruleset ($policy)" + + local reviews=0 + [ "$policy" = "pr-required" ] && reviews=1 + + local checks_json + if [ -n "$checks_csv" ]; then + checks_json=$(echo "$checks_csv" | tr '|' '\n' | jq -R '{context:.}' | jq -s .) + else + checks_json="[]" + fi + + local existing + existing=$(gh api "/repos/$ORG/$repo/rulesets" --jq ".[] | select(.name==\"$RULESET_NAME\") | .id" 2>/dev/null || true) + if [ -n "$existing" ]; then + echo " existing ruleset id=$existing โ€” replacing" + run gh api -X DELETE "/repos/$ORG/$repo/rulesets/$existing" + fi + + local body + body=$(jq --argjson reviews "$reviews" --argjson checks "$checks_json" ' + .rules |= map( + if .type == "pull_request" then + .parameters.required_approving_review_count = $reviews + elif .type == "required_status_checks" then + .parameters.required_status_checks = $checks + else + . + end + ) + ' "$TEMPLATE") + + if [ "$DRY" = "1" ]; then + echo " [dry] POST /repos/$ORG/$repo/rulesets with body:" + echo "$body" | sed 's/^/ /' + else + echo "$body" | gh api -X POST "/repos/$ORG/$repo/rulesets" --input - > /dev/null + echo " ruleset created" + fi +} + +for line in "${MATRIX[@]}"; do + repo="${line%%:*}" + rest="${line#*:}" + policy="${rest%%:*}" + checks="${rest#*:}" + + if [ -n "$TARGET" ] && [ "$TARGET" != "$repo" ]; then + continue + fi + + apply_repo_settings "$repo" + if [ "$policy" != "settings-only" ]; then + apply_ruleset "$repo" "$policy" "$checks" + fi + echo +done + +if [ "$DRY" = "1" ]; then + echo "Dry run complete. Re-run with --apply to write." +fi From 69d48b94e10bb0f92a6b8013f738d017a8e3e1ab Mon Sep 17 00:00:00 2001 From: Vladimir Klimontovich Date: Mon, 11 May 2026 14:33:33 -0400 Subject: [PATCH 2/3] chore: drop apply-rulesets script (not committed; applied once locally) --- scripts/apply-rulesets.sh | 126 -------------------------------------- 1 file changed, 126 deletions(-) delete mode 100644 scripts/apply-rulesets.sh diff --git a/scripts/apply-rulesets.sh b/scripts/apply-rulesets.sh deleted file mode 100644 index 3ebec07..0000000 --- a/scripts/apply-rulesets.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env bash -# Apply branch rulesets + repo settings across jitsucom repos. -# -# Dry by default. Pass --apply to write. -# -# Required: gh auth status as a user with Admin on each target repo. -# Idempotent: deletes existing ruleset named "default-branch-protection" before recreating. -# -# Usage: -# scripts/apply-rulesets.sh # dry, all repos -# scripts/apply-rulesets.sh --apply # write, all repos -# scripts/apply-rulesets.sh --apply jitsu # write, single repo - -set -euo pipefail - -ORG=jitsucom -RULESET_NAME=default-branch-protection -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -TEMPLATE="$SCRIPT_DIR/../rulesets/template.json" - -# Format: repo:policy:pipe-separated-check-contexts -# Policy: flexible | pr-required | settings-only -MATRIX=( - "jitsu:flexible:โœจ Lint & Test|๐Ÿงช Bulker Test|ai-review / ai-review" - "jitsu-cloud-infra:flexible:ai-review / ai-review" - "github-workflows:flexible:ai-review" - "jitsu-bi:flexible:" - "mongobetween:pr-required:tests|lint|salus" - "websites:settings-only:" -) - -DRY=1 -TARGET="" -for arg in "$@"; do - case "$arg" in - --apply) DRY=0 ;; - --help|-h) - sed -n '2,/^set -e/p' "$0" | sed 's/^# \{0,1\}//' | sed '$d' - exit 0 - ;; - *) TARGET="$arg" ;; - esac -done - -run() { - if [ "$DRY" = "1" ]; then - printf ' [dry] '; printf '%q ' "$@"; echo - else - "$@" - fi -} - -apply_repo_settings() { - local repo=$1 - echo "โ†’ $repo: repo settings (auto-merge on, rebase only, delete branch on merge)" - run gh api -X PATCH "/repos/$ORG/$repo" \ - -F allow_auto_merge=true \ - -F allow_rebase_merge=true \ - -F allow_squash_merge=false \ - -F allow_merge_commit=false \ - -F delete_branch_on_merge=true -} - -apply_ruleset() { - local repo=$1 policy=$2 checks_csv=$3 - echo "โ†’ $repo: ruleset ($policy)" - - local reviews=0 - [ "$policy" = "pr-required" ] && reviews=1 - - local checks_json - if [ -n "$checks_csv" ]; then - checks_json=$(echo "$checks_csv" | tr '|' '\n' | jq -R '{context:.}' | jq -s .) - else - checks_json="[]" - fi - - local existing - existing=$(gh api "/repos/$ORG/$repo/rulesets" --jq ".[] | select(.name==\"$RULESET_NAME\") | .id" 2>/dev/null || true) - if [ -n "$existing" ]; then - echo " existing ruleset id=$existing โ€” replacing" - run gh api -X DELETE "/repos/$ORG/$repo/rulesets/$existing" - fi - - local body - body=$(jq --argjson reviews "$reviews" --argjson checks "$checks_json" ' - .rules |= map( - if .type == "pull_request" then - .parameters.required_approving_review_count = $reviews - elif .type == "required_status_checks" then - .parameters.required_status_checks = $checks - else - . - end - ) - ' "$TEMPLATE") - - if [ "$DRY" = "1" ]; then - echo " [dry] POST /repos/$ORG/$repo/rulesets with body:" - echo "$body" | sed 's/^/ /' - else - echo "$body" | gh api -X POST "/repos/$ORG/$repo/rulesets" --input - > /dev/null - echo " ruleset created" - fi -} - -for line in "${MATRIX[@]}"; do - repo="${line%%:*}" - rest="${line#*:}" - policy="${rest%%:*}" - checks="${rest#*:}" - - if [ -n "$TARGET" ] && [ "$TARGET" != "$repo" ]; then - continue - fi - - apply_repo_settings "$repo" - if [ "$policy" != "settings-only" ]; then - apply_ruleset "$repo" "$policy" "$checks" - fi - echo -done - -if [ "$DRY" = "1" ]; then - echo "Dry run complete. Re-run with --apply to write." -fi From 8a08d9e3737bb6213bca0fc16cc777202064414f Mon Sep 17 00:00:00 2001 From: Vladimir Klimontovich Date: Mon, 11 May 2026 14:33:35 -0400 Subject: [PATCH 3/3] chore: drop ruleset template (not committed; applied once locally) --- rulesets/template.json | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 rulesets/template.json diff --git a/rulesets/template.json b/rulesets/template.json deleted file mode 100644 index a0e517d..0000000 --- a/rulesets/template.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "default-branch-protection", - "target": "branch", - "enforcement": "active", - "conditions": { - "ref_name": { - "include": ["~DEFAULT_BRANCH"], - "exclude": [] - } - }, - "bypass_actors": [ - { - "actor_id": 5, - "actor_type": "RepositoryRole", - "bypass_mode": "always" - } - ], - "rules": [ - { "type": "deletion" }, - { "type": "non_fast_forward" }, - { - "type": "pull_request", - "parameters": { - "required_approving_review_count": 0, - "dismiss_stale_reviews_on_push": true, - "require_code_owner_review": false, - "require_last_push_approval": false, - "required_review_thread_resolution": false, - "allowed_merge_methods": ["rebase"] - } - }, - { - "type": "required_status_checks", - "parameters": { - "strict_required_status_checks_policy": false, - "do_not_enforce_on_create": false, - "required_status_checks": [] - } - } - ] -}