@codyswann/lisa 2.195.7 → 2.196.0
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/all/github-rulesets/base.json +64 -0
- package/all/github-rulesets/prevent-delete.json +25 -0
- package/all/github-rulesets/protect-tags.json +31 -0
- package/cdk/github-rulesets/cdk-validation.json +43 -0
- package/expo/create-only/.github/workflows/deploy.yml +6 -1
- package/expo/github-rulesets/playwright.json +38 -0
- package/package.json +2 -2
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-github-repo/SKILL.md +66 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-github-repo/agents/openai.yaml +4 -0
- package/plugins/lisa/commands/lisa/setup/github-repo.md +7 -0
- package/plugins/lisa/skills/lisa-setup-github-repo/SKILL.md +66 -0
- package/plugins/lisa/skills/lisa-setup-github-repo/agents/openai.yaml +4 -0
- package/plugins/lisa-agy/commands/lisa/setup/github-repo.md +7 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-setup-github-repo/SKILL.md +66 -0
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/commands/lisa/setup/github-repo.md +7 -0
- package/plugins/lisa-copilot/skills/lisa-setup-github-repo/SKILL.md +66 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/commands/lisa/setup/github-repo.md +7 -0
- package/plugins/lisa-cursor/skills/lisa-setup-github-repo/SKILL.md +66 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/commands/lisa/setup/github-repo.md +7 -0
- package/plugins/src/base/skills/lisa-setup-github-repo/SKILL.md +66 -0
- package/rails/github-rulesets/quality-checks.json +51 -0
- package/scripts/cleanup-github-branches.sh +96 -72
- package/scripts/cleanup-local-merged-branches.sh +110 -0
- package/scripts/cleanup-worktrees.sh +222 -0
- package/scripts/lisa-github-repo-settings.sh +152 -0
- package/scripts/lisa-github-repo-setup.sh +63 -0
- package/scripts/lisa-github-rulesets.sh +549 -0
- package/scripts/setup-deploy-key.sh +61 -2
- package/typescript/github-rulesets/quality-checks.json +67 -0
- package/typescript/github-rulesets/base.json +0 -33
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# lisa-github-rulesets.sh
|
|
4
|
+
#
|
|
5
|
+
# Applies GitHub repository rulesets from Lisa's project type directories.
|
|
6
|
+
# Reads ruleset templates from github-rulesets/ folders and uses the gh CLI
|
|
7
|
+
# to create or update them on the target repository.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# lisa-github-rulesets.sh [options] [project-path]
|
|
11
|
+
#
|
|
12
|
+
# Options:
|
|
13
|
+
# -n, --dry-run Show what would be done without making changes
|
|
14
|
+
# -y, --yes Non-interactive mode (skip confirmations)
|
|
15
|
+
# -v, --verbose Show detailed output
|
|
16
|
+
# -h, --help Show this help message
|
|
17
|
+
#
|
|
18
|
+
# Requires:
|
|
19
|
+
# - gh CLI (authenticated with repo admin permissions)
|
|
20
|
+
# - jq
|
|
21
|
+
#
|
|
22
|
+
|
|
23
|
+
set -eo pipefail
|
|
24
|
+
|
|
25
|
+
# Colors
|
|
26
|
+
RED='\033[0;31m'
|
|
27
|
+
GREEN='\033[0;32m'
|
|
28
|
+
YELLOW='\033[0;33m'
|
|
29
|
+
BLUE='\033[0;34m'
|
|
30
|
+
NC='\033[0m' # No Color
|
|
31
|
+
|
|
32
|
+
# Script directory (this file lives in scripts/; templates live one level up)
|
|
33
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
34
|
+
LISA_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
35
|
+
|
|
36
|
+
# Default options
|
|
37
|
+
DRY_RUN=false
|
|
38
|
+
YES_MODE=false
|
|
39
|
+
VERBOSE=false
|
|
40
|
+
PROJECT_PATH=""
|
|
41
|
+
|
|
42
|
+
# Project type hierarchy (child -> parent)
|
|
43
|
+
# Using a function to avoid associative array issues with set -u
|
|
44
|
+
get_parent_type() {
|
|
45
|
+
local type="$1"
|
|
46
|
+
case "$type" in
|
|
47
|
+
typescript) echo "" ;;
|
|
48
|
+
npm-package) echo "typescript" ;;
|
|
49
|
+
expo) echo "typescript" ;;
|
|
50
|
+
nestjs) echo "typescript" ;;
|
|
51
|
+
cdk) echo "typescript" ;;
|
|
52
|
+
rails) echo "" ;;
|
|
53
|
+
*) echo "" ;;
|
|
54
|
+
esac
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
##############################################################################
|
|
58
|
+
# Utility Functions
|
|
59
|
+
##############################################################################
|
|
60
|
+
|
|
61
|
+
log_info() {
|
|
62
|
+
echo -e "${BLUE}ℹ${NC} $1"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
log_success() {
|
|
66
|
+
echo -e "${GREEN}✓${NC} $1"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
log_warning() {
|
|
70
|
+
echo -e "${YELLOW}⚠${NC} $1"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
log_error() {
|
|
74
|
+
echo -e "${RED}✗${NC} $1" >&2
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
log_verbose() {
|
|
78
|
+
if [[ "$VERBOSE" == "true" ]]; then
|
|
79
|
+
echo -e " $1"
|
|
80
|
+
fi
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
show_help() {
|
|
84
|
+
cat << 'EOF'
|
|
85
|
+
lisa-github-rulesets.sh - Apply GitHub repository rulesets from Lisa templates
|
|
86
|
+
|
|
87
|
+
USAGE:
|
|
88
|
+
lisa-github-rulesets.sh [OPTIONS] [PROJECT_PATH]
|
|
89
|
+
|
|
90
|
+
ARGUMENTS:
|
|
91
|
+
PROJECT_PATH Path to the project (defaults to current directory)
|
|
92
|
+
|
|
93
|
+
OPTIONS:
|
|
94
|
+
-n, --dry-run Show what would be done without making API calls
|
|
95
|
+
-y, --yes Non-interactive mode (skip confirmations)
|
|
96
|
+
-v, --verbose Show detailed output
|
|
97
|
+
-h, --help Show this help message
|
|
98
|
+
|
|
99
|
+
DESCRIPTION:
|
|
100
|
+
Reads ruleset templates from Lisa's project type directories
|
|
101
|
+
(all/github-rulesets/, typescript/github-rulesets/, etc.) and
|
|
102
|
+
applies them to the target repository using the GitHub API.
|
|
103
|
+
|
|
104
|
+
Templates are applied in order: all -> parent types -> specific type.
|
|
105
|
+
If a ruleset with the same name exists, it will be updated.
|
|
106
|
+
|
|
107
|
+
REQUIREMENTS:
|
|
108
|
+
- gh CLI must be installed and authenticated
|
|
109
|
+
- User must have admin permissions on the repository
|
|
110
|
+
- jq must be installed for JSON processing
|
|
111
|
+
|
|
112
|
+
EXAMPLES:
|
|
113
|
+
# Apply rulesets to current directory's repo
|
|
114
|
+
lisa-github-rulesets.sh
|
|
115
|
+
|
|
116
|
+
# Dry run to see what would happen
|
|
117
|
+
lisa-github-rulesets.sh --dry-run /path/to/project
|
|
118
|
+
|
|
119
|
+
# Non-interactive mode
|
|
120
|
+
lisa-github-rulesets.sh --yes /path/to/project
|
|
121
|
+
EOF
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
##############################################################################
|
|
125
|
+
# Prerequisite Checks
|
|
126
|
+
##############################################################################
|
|
127
|
+
|
|
128
|
+
check_prerequisites() {
|
|
129
|
+
local missing=()
|
|
130
|
+
|
|
131
|
+
if ! command -v gh &> /dev/null; then
|
|
132
|
+
missing+=("gh (GitHub CLI)")
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
if ! command -v jq &> /dev/null; then
|
|
136
|
+
missing+=("jq")
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
140
|
+
log_error "Missing required tools: ${missing[*]}"
|
|
141
|
+
exit 1
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# Check gh authentication
|
|
145
|
+
if ! gh auth status &> /dev/null; then
|
|
146
|
+
log_error "GitHub CLI is not authenticated. Run 'gh auth login' first."
|
|
147
|
+
exit 1
|
|
148
|
+
fi
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
##############################################################################
|
|
152
|
+
# Project Detection
|
|
153
|
+
##############################################################################
|
|
154
|
+
|
|
155
|
+
detect_project_types() {
|
|
156
|
+
local project_path="$1"
|
|
157
|
+
local detected_types=()
|
|
158
|
+
|
|
159
|
+
# TypeScript detection
|
|
160
|
+
if [[ -f "$project_path/tsconfig.json" ]]; then
|
|
161
|
+
detected_types+=("typescript")
|
|
162
|
+
elif [[ -f "$project_path/package.json" ]]; then
|
|
163
|
+
if jq -e '.devDependencies.typescript // .dependencies.typescript' "$project_path/package.json" &> /dev/null; then
|
|
164
|
+
detected_types+=("typescript")
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
# npm-package detection (not private and has main/bin/exports/files)
|
|
169
|
+
if [[ -f "$project_path/package.json" ]]; then
|
|
170
|
+
local is_private
|
|
171
|
+
is_private=$(jq -r '.private // false' "$project_path/package.json")
|
|
172
|
+
if [[ "$is_private" != "true" ]]; then
|
|
173
|
+
if jq -e '.main // .bin // .exports // .files' "$project_path/package.json" &> /dev/null; then
|
|
174
|
+
detected_types+=("npm-package")
|
|
175
|
+
fi
|
|
176
|
+
fi
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# Expo detection
|
|
180
|
+
if [[ -f "$project_path/app.json" ]] || [[ -f "$project_path/eas.json" ]]; then
|
|
181
|
+
detected_types+=("expo")
|
|
182
|
+
elif [[ -f "$project_path/package.json" ]]; then
|
|
183
|
+
if jq -e '.dependencies.expo // .devDependencies.expo' "$project_path/package.json" &> /dev/null; then
|
|
184
|
+
detected_types+=("expo")
|
|
185
|
+
fi
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# NestJS detection
|
|
189
|
+
if [[ -f "$project_path/nest-cli.json" ]]; then
|
|
190
|
+
detected_types+=("nestjs")
|
|
191
|
+
elif [[ -f "$project_path/package.json" ]]; then
|
|
192
|
+
if jq -e '.dependencies["@nestjs/core"] // .devDependencies["@nestjs/core"]' "$project_path/package.json" &> /dev/null; then
|
|
193
|
+
detected_types+=("nestjs")
|
|
194
|
+
fi
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# CDK detection
|
|
198
|
+
if [[ -f "$project_path/cdk.json" ]]; then
|
|
199
|
+
detected_types+=("cdk")
|
|
200
|
+
elif [[ -f "$project_path/package.json" ]]; then
|
|
201
|
+
if jq -e '.dependencies["aws-cdk-lib"] // .devDependencies["aws-cdk-lib"]' "$project_path/package.json" &> /dev/null; then
|
|
202
|
+
detected_types+=("cdk")
|
|
203
|
+
fi
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# Rails detection
|
|
207
|
+
if [[ -f "$project_path/Gemfile" ]] && grep -q "rails" "$project_path/Gemfile" 2>/dev/null; then
|
|
208
|
+
detected_types+=("rails")
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
echo "${detected_types[@]}"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
expand_types_with_parents() {
|
|
215
|
+
local -a input_types=("$@")
|
|
216
|
+
local -a expanded=()
|
|
217
|
+
local seen=""
|
|
218
|
+
|
|
219
|
+
for type in "${input_types[@]}"; do
|
|
220
|
+
local current="$type"
|
|
221
|
+
local -a chain=()
|
|
222
|
+
|
|
223
|
+
# Build chain from type to root
|
|
224
|
+
while [[ -n "$current" ]]; do
|
|
225
|
+
chain+=("$current")
|
|
226
|
+
current=$(get_parent_type "$current")
|
|
227
|
+
done
|
|
228
|
+
|
|
229
|
+
# Add in reverse order (parent first)
|
|
230
|
+
for ((i=${#chain[@]}-1; i>=0; i--)); do
|
|
231
|
+
local t="${chain[$i]}"
|
|
232
|
+
if [[ ! " $seen " =~ " $t " ]]; then
|
|
233
|
+
expanded+=("$t")
|
|
234
|
+
seen="$seen $t"
|
|
235
|
+
fi
|
|
236
|
+
done
|
|
237
|
+
done
|
|
238
|
+
|
|
239
|
+
echo "${expanded[@]}"
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
##############################################################################
|
|
243
|
+
# Repository Info
|
|
244
|
+
##############################################################################
|
|
245
|
+
|
|
246
|
+
get_repo_info() {
|
|
247
|
+
local project_path="$1"
|
|
248
|
+
|
|
249
|
+
cd "$project_path"
|
|
250
|
+
|
|
251
|
+
if ! git rev-parse --git-dir &> /dev/null; then
|
|
252
|
+
log_error "Not a git repository: $project_path"
|
|
253
|
+
exit 1
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
local repo_info
|
|
257
|
+
repo_info=$(gh repo view --json nameWithOwner -q '.nameWithOwner' 2>/dev/null) || {
|
|
258
|
+
log_error "Could not determine repository. Make sure you're in a git repo with a GitHub remote."
|
|
259
|
+
exit 1
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
echo "$repo_info"
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
##############################################################################
|
|
266
|
+
# Ruleset Operations
|
|
267
|
+
##############################################################################
|
|
268
|
+
|
|
269
|
+
find_ruleset_by_name() {
|
|
270
|
+
local rulesets_json="$1"
|
|
271
|
+
local name="$2"
|
|
272
|
+
echo "$rulesets_json" | jq -r --arg name "$name" '.[] | select(.name == $name) | .id // empty'
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
strip_readonly_fields() {
|
|
276
|
+
local json="$1"
|
|
277
|
+
# Remove fields that are read-only or repository-specific
|
|
278
|
+
echo "$json" | jq 'del(.id, .source_type, .source, .node_id, .created_at, .updated_at, ._links, .current_user_can_bypass)'
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
# GitHub Actions integration id — its checks only ever report when the project
|
|
282
|
+
# actually has workflows, so requiring them on a workflow-less repo (e.g. a
|
|
283
|
+
# wiki) would block every PR forever.
|
|
284
|
+
ACTIONS_INTEGRATION_ID=15368
|
|
285
|
+
|
|
286
|
+
strip_actions_checks_if_no_workflows() {
|
|
287
|
+
local json="$1"
|
|
288
|
+
local project_path="$2"
|
|
289
|
+
|
|
290
|
+
if [[ -d "$project_path/.github/workflows" ]]; then
|
|
291
|
+
echo "$json"
|
|
292
|
+
return 0
|
|
293
|
+
fi
|
|
294
|
+
|
|
295
|
+
echo "$json" | jq --argjson actions "$ACTIONS_INTEGRATION_ID" '
|
|
296
|
+
if .rules then
|
|
297
|
+
.rules |= (
|
|
298
|
+
map(
|
|
299
|
+
if .type == "required_status_checks" then
|
|
300
|
+
.parameters.required_status_checks |=
|
|
301
|
+
map(select(.integration_id != $actions))
|
|
302
|
+
else . end
|
|
303
|
+
)
|
|
304
|
+
| map(select(
|
|
305
|
+
.type != "required_status_checks"
|
|
306
|
+
or (.parameters.required_status_checks | length) > 0
|
|
307
|
+
))
|
|
308
|
+
)
|
|
309
|
+
else . end'
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
apply_ruleset() {
|
|
313
|
+
local repo="$1"
|
|
314
|
+
local template_file="$2"
|
|
315
|
+
local existing_rulesets="$3"
|
|
316
|
+
local project_path="$4"
|
|
317
|
+
|
|
318
|
+
local template_content
|
|
319
|
+
template_content=$(cat "$template_file")
|
|
320
|
+
|
|
321
|
+
local ruleset_name
|
|
322
|
+
ruleset_name=$(echo "$template_content" | jq -r '.name')
|
|
323
|
+
|
|
324
|
+
local clean_template
|
|
325
|
+
clean_template=$(strip_readonly_fields "$template_content")
|
|
326
|
+
clean_template=$(strip_actions_checks_if_no_workflows "$clean_template" "$project_path")
|
|
327
|
+
|
|
328
|
+
# A template whose rules were entirely stripped has nothing to enforce here.
|
|
329
|
+
if [[ $(echo "$clean_template" | jq '(.rules // [1]) | length') -eq 0 ]]; then
|
|
330
|
+
log_warning "Skipping ruleset '$ruleset_name' — no applicable rules for this project (no workflows)"
|
|
331
|
+
return 0
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
local existing_id
|
|
335
|
+
existing_id=$(find_ruleset_by_name "$existing_rulesets" "$ruleset_name")
|
|
336
|
+
|
|
337
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
338
|
+
if [[ -n "$existing_id" ]]; then
|
|
339
|
+
log_info "[DRY RUN] Would update ruleset '$ruleset_name' (id: $existing_id)"
|
|
340
|
+
else
|
|
341
|
+
log_info "[DRY RUN] Would create ruleset '$ruleset_name'"
|
|
342
|
+
fi
|
|
343
|
+
log_verbose "Template: $template_file"
|
|
344
|
+
return 0
|
|
345
|
+
fi
|
|
346
|
+
|
|
347
|
+
# Create temp file for the request body
|
|
348
|
+
local temp_file
|
|
349
|
+
temp_file=$(mktemp)
|
|
350
|
+
echo "$clean_template" > "$temp_file"
|
|
351
|
+
|
|
352
|
+
if [[ -n "$existing_id" ]]; then
|
|
353
|
+
log_info "Updating ruleset '$ruleset_name' (id: $existing_id)..."
|
|
354
|
+
if gh api -X PUT "repos/$repo/rulesets/$existing_id" --input "$temp_file" > /dev/null; then
|
|
355
|
+
log_success "Updated ruleset '$ruleset_name'"
|
|
356
|
+
else
|
|
357
|
+
log_error "Failed to update ruleset '$ruleset_name'"
|
|
358
|
+
rm -f "$temp_file"
|
|
359
|
+
return 1
|
|
360
|
+
fi
|
|
361
|
+
else
|
|
362
|
+
log_info "Creating ruleset '$ruleset_name'..."
|
|
363
|
+
if gh api -X POST "repos/$repo/rulesets" --input "$temp_file" > /dev/null; then
|
|
364
|
+
log_success "Created ruleset '$ruleset_name'"
|
|
365
|
+
else
|
|
366
|
+
log_error "Failed to create ruleset '$ruleset_name'"
|
|
367
|
+
rm -f "$temp_file"
|
|
368
|
+
return 1
|
|
369
|
+
fi
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
rm -f "$temp_file"
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
##############################################################################
|
|
376
|
+
# Main Logic
|
|
377
|
+
##############################################################################
|
|
378
|
+
|
|
379
|
+
collect_templates() {
|
|
380
|
+
local -a types=("$@")
|
|
381
|
+
local -a templates=()
|
|
382
|
+
|
|
383
|
+
# Always include 'all' first
|
|
384
|
+
local all_dir="$LISA_ROOT/all/github-rulesets"
|
|
385
|
+
if [[ -d "$all_dir" ]]; then
|
|
386
|
+
for file in "$all_dir"/*.json; do
|
|
387
|
+
[[ -f "$file" ]] && templates+=("$file")
|
|
388
|
+
done
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
# Then add type-specific templates in order
|
|
392
|
+
for type in "${types[@]}"; do
|
|
393
|
+
local type_dir="$LISA_ROOT/$type/github-rulesets"
|
|
394
|
+
if [[ -d "$type_dir" ]]; then
|
|
395
|
+
for file in "$type_dir"/*.json; do
|
|
396
|
+
[[ -f "$file" ]] && templates+=("$file")
|
|
397
|
+
done
|
|
398
|
+
fi
|
|
399
|
+
done
|
|
400
|
+
|
|
401
|
+
echo "${templates[@]}"
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
main() {
|
|
405
|
+
# Parse arguments
|
|
406
|
+
while [[ $# -gt 0 ]]; do
|
|
407
|
+
case $1 in
|
|
408
|
+
-n|--dry-run)
|
|
409
|
+
DRY_RUN=true
|
|
410
|
+
shift
|
|
411
|
+
;;
|
|
412
|
+
-y|--yes)
|
|
413
|
+
YES_MODE=true
|
|
414
|
+
shift
|
|
415
|
+
;;
|
|
416
|
+
-v|--verbose)
|
|
417
|
+
VERBOSE=true
|
|
418
|
+
shift
|
|
419
|
+
;;
|
|
420
|
+
-h|--help)
|
|
421
|
+
show_help
|
|
422
|
+
exit 0
|
|
423
|
+
;;
|
|
424
|
+
-*)
|
|
425
|
+
log_error "Unknown option: $1"
|
|
426
|
+
show_help
|
|
427
|
+
exit 1
|
|
428
|
+
;;
|
|
429
|
+
*)
|
|
430
|
+
PROJECT_PATH="$1"
|
|
431
|
+
shift
|
|
432
|
+
;;
|
|
433
|
+
esac
|
|
434
|
+
done
|
|
435
|
+
|
|
436
|
+
# Default to current directory
|
|
437
|
+
PROJECT_PATH="${PROJECT_PATH:-.}"
|
|
438
|
+
PROJECT_PATH="$(cd "$PROJECT_PATH" && pwd)"
|
|
439
|
+
|
|
440
|
+
log_info "Lisa GitHub Rulesets"
|
|
441
|
+
echo ""
|
|
442
|
+
|
|
443
|
+
# Check prerequisites
|
|
444
|
+
check_prerequisites
|
|
445
|
+
|
|
446
|
+
# Get repository info
|
|
447
|
+
local repo
|
|
448
|
+
repo=$(get_repo_info "$PROJECT_PATH")
|
|
449
|
+
log_info "Repository: $repo"
|
|
450
|
+
|
|
451
|
+
# Detect project types
|
|
452
|
+
local detected_types_str
|
|
453
|
+
detected_types_str=$(detect_project_types "$PROJECT_PATH")
|
|
454
|
+
|
|
455
|
+
if [[ -z "$detected_types_str" ]]; then
|
|
456
|
+
log_warning "No specific project types detected, using 'all' templates only"
|
|
457
|
+
detected_types_str=""
|
|
458
|
+
fi
|
|
459
|
+
|
|
460
|
+
# Convert to array safely
|
|
461
|
+
local -a detected_types=()
|
|
462
|
+
if [[ -n "$detected_types_str" ]]; then
|
|
463
|
+
read -ra detected_types <<< "$detected_types_str"
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
# Expand with parent types
|
|
467
|
+
local -a expanded_types=()
|
|
468
|
+
if [[ ${#detected_types[@]} -gt 0 ]]; then
|
|
469
|
+
local expanded_str
|
|
470
|
+
expanded_str=$(expand_types_with_parents "${detected_types[@]}")
|
|
471
|
+
read -ra expanded_types <<< "$expanded_str"
|
|
472
|
+
fi
|
|
473
|
+
|
|
474
|
+
if [[ ${#expanded_types[@]} -gt 0 ]]; then
|
|
475
|
+
log_info "Detected types: ${expanded_types[*]}"
|
|
476
|
+
fi
|
|
477
|
+
|
|
478
|
+
# Collect templates
|
|
479
|
+
local templates_str
|
|
480
|
+
templates_str=$(collect_templates "${expanded_types[@]}")
|
|
481
|
+
|
|
482
|
+
local -a templates=()
|
|
483
|
+
if [[ -n "$templates_str" ]]; then
|
|
484
|
+
read -ra templates <<< "$templates_str"
|
|
485
|
+
fi
|
|
486
|
+
|
|
487
|
+
if [[ ${#templates[@]} -eq 0 ]]; then
|
|
488
|
+
log_warning "No ruleset templates found"
|
|
489
|
+
exit 0
|
|
490
|
+
fi
|
|
491
|
+
|
|
492
|
+
log_info "Found ${#templates[@]} ruleset template(s)"
|
|
493
|
+
for t in "${templates[@]}"; do
|
|
494
|
+
log_verbose " - $t"
|
|
495
|
+
done
|
|
496
|
+
echo ""
|
|
497
|
+
|
|
498
|
+
# Confirmation prompt
|
|
499
|
+
if [[ "$DRY_RUN" == "false" ]] && [[ "$YES_MODE" == "false" ]]; then
|
|
500
|
+
echo -n "Apply rulesets to $repo? [y/N] "
|
|
501
|
+
read -r response
|
|
502
|
+
if [[ ! "$response" =~ ^[Yy]$ ]]; then
|
|
503
|
+
log_info "Aborted"
|
|
504
|
+
exit 0
|
|
505
|
+
fi
|
|
506
|
+
echo ""
|
|
507
|
+
fi
|
|
508
|
+
|
|
509
|
+
# Get existing rulesets — a 403 here means the plan doesn't support rulesets
|
|
510
|
+
# (private repo on a free personal plan); that's a skip, not a failure.
|
|
511
|
+
log_info "Fetching existing rulesets..."
|
|
512
|
+
local existing_rulesets
|
|
513
|
+
if ! existing_rulesets=$(gh api "repos/$repo/rulesets" 2>&1); then
|
|
514
|
+
if echo "$existing_rulesets" | grep -qi "upgrade to github\|HTTP 403"; then
|
|
515
|
+
log_warning "Rulesets are not available on this repository's plan — skipping"
|
|
516
|
+
exit 0
|
|
517
|
+
fi
|
|
518
|
+
log_error "Could not fetch rulesets: $existing_rulesets"
|
|
519
|
+
exit 1
|
|
520
|
+
fi
|
|
521
|
+
local existing_count
|
|
522
|
+
existing_count=$(echo "$existing_rulesets" | jq 'length')
|
|
523
|
+
log_verbose "Found $existing_count existing ruleset(s)"
|
|
524
|
+
|
|
525
|
+
# Apply each template
|
|
526
|
+
local success_count=0
|
|
527
|
+
local fail_count=0
|
|
528
|
+
|
|
529
|
+
for template in "${templates[@]}"; do
|
|
530
|
+
if apply_ruleset "$repo" "$template" "$existing_rulesets" "$PROJECT_PATH"; then
|
|
531
|
+
success_count=$((success_count + 1))
|
|
532
|
+
else
|
|
533
|
+
fail_count=$((fail_count + 1))
|
|
534
|
+
fi
|
|
535
|
+
done
|
|
536
|
+
|
|
537
|
+
echo ""
|
|
538
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
539
|
+
log_info "Dry run complete. ${#templates[@]} ruleset(s) would be applied."
|
|
540
|
+
else
|
|
541
|
+
log_success "Applied $success_count ruleset(s)"
|
|
542
|
+
if [[ $fail_count -gt 0 ]]; then
|
|
543
|
+
log_warning "$fail_count ruleset(s) failed"
|
|
544
|
+
exit 1
|
|
545
|
+
fi
|
|
546
|
+
fi
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
main "$@"
|
|
@@ -6,15 +6,27 @@
|
|
|
6
6
|
# with write access, enabling GitHub Actions to push to protected branches.
|
|
7
7
|
#
|
|
8
8
|
# Usage:
|
|
9
|
-
# ./scripts/setup-deploy-key.sh
|
|
9
|
+
# ./scripts/setup-deploy-key.sh [--yes]
|
|
10
|
+
#
|
|
11
|
+
# Options:
|
|
12
|
+
# -y, --yes Non-interactive mode: skips if a deploy key + DEPLOY_KEY
|
|
13
|
+
# secret already exist, otherwise creates both without
|
|
14
|
+
# prompting and never leaves key files on disk.
|
|
10
15
|
#
|
|
11
16
|
# Requirements:
|
|
12
17
|
# - ssh-keygen (usually pre-installed)
|
|
13
|
-
# - gh CLI (optional, for automatic setup)
|
|
18
|
+
# - gh CLI (optional, for automatic setup; required for --yes)
|
|
14
19
|
#
|
|
15
20
|
|
|
16
21
|
set -euo pipefail
|
|
17
22
|
|
|
23
|
+
YES_MODE=false
|
|
24
|
+
for arg in "$@"; do
|
|
25
|
+
case "$arg" in
|
|
26
|
+
-y|--yes) YES_MODE=true ;;
|
|
27
|
+
esac
|
|
28
|
+
done
|
|
29
|
+
|
|
18
30
|
# Colors for output
|
|
19
31
|
RED='\033[0;31m'
|
|
20
32
|
GREEN='\033[0;32m'
|
|
@@ -68,6 +80,53 @@ echo ""
|
|
|
68
80
|
echo "Repository: $OWNER/$REPO"
|
|
69
81
|
echo ""
|
|
70
82
|
|
|
83
|
+
# Non-interactive mode: idempotent — skip when already configured, and keep
|
|
84
|
+
# generated key material in a private temp dir that is always cleaned up.
|
|
85
|
+
KEY_TITLE="GitHub Actions Deploy Key"
|
|
86
|
+
if [[ "$YES_MODE" == "true" ]]; then
|
|
87
|
+
if ! command -v gh &>/dev/null || ! gh auth status &>/dev/null; then
|
|
88
|
+
print_error "--yes mode requires an authenticated gh CLI."
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
HAS_KEY=$(gh repo deploy-key list --repo "$OWNER/$REPO" --json title \
|
|
93
|
+
--jq --arg t "$KEY_TITLE" '[.[] | select(.title == $t)] | length' 2>/dev/null || echo 0)
|
|
94
|
+
HAS_SECRET=$(gh secret list --repo "$OWNER/$REPO" --json name \
|
|
95
|
+
--jq '[.[] | select(.name == "DEPLOY_KEY")] | length' 2>/dev/null || echo 0)
|
|
96
|
+
if [[ "$HAS_KEY" -gt 0 && "$HAS_SECRET" -gt 0 ]]; then
|
|
97
|
+
print_success "Deploy key and DEPLOY_KEY secret already configured — nothing to do."
|
|
98
|
+
exit 0
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
TEMP_KEY_DIR=$(mktemp -d)
|
|
102
|
+
trap 'rm -rf "$TEMP_KEY_DIR"' EXIT
|
|
103
|
+
KEY_FILE="$TEMP_KEY_DIR/deploy_key"
|
|
104
|
+
|
|
105
|
+
print_step "Generating SSH key pair..."
|
|
106
|
+
ssh-keygen -t ed25519 -C "github-actions-deploy-key-${REPO}" -f "$KEY_FILE" -N "" -q
|
|
107
|
+
|
|
108
|
+
# A stale key under the same title blocks re-adding; replace it.
|
|
109
|
+
if [[ "$HAS_KEY" -gt 0 ]]; then
|
|
110
|
+
gh repo deploy-key list --repo "$OWNER/$REPO" --json id,title \
|
|
111
|
+
--jq --arg t "$KEY_TITLE" '.[] | select(.title == $t) | .id' 2>/dev/null \
|
|
112
|
+
| while read -r key_id; do
|
|
113
|
+
gh repo deploy-key delete "$key_id" --repo "$OWNER/$REPO" || true
|
|
114
|
+
done
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
print_step "Adding deploy key to repository..."
|
|
118
|
+
gh repo deploy-key add "${KEY_FILE}.pub" --repo "$OWNER/$REPO" \
|
|
119
|
+
--title "$KEY_TITLE" --allow-write
|
|
120
|
+
print_success "Deploy key added with write access"
|
|
121
|
+
|
|
122
|
+
print_step "Adding DEPLOY_KEY secret..."
|
|
123
|
+
gh secret set DEPLOY_KEY --repo "$OWNER/$REPO" < "$KEY_FILE"
|
|
124
|
+
print_success "DEPLOY_KEY secret added"
|
|
125
|
+
|
|
126
|
+
print_success "Deploy key setup complete!"
|
|
127
|
+
exit 0
|
|
128
|
+
fi
|
|
129
|
+
|
|
71
130
|
# Check for existing key
|
|
72
131
|
KEY_FILE="deploy_key"
|
|
73
132
|
if [[ -f "$KEY_FILE" ]] || [[ -f "${KEY_FILE}.pub" ]]; then
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "quality checks",
|
|
3
|
+
"target": "branch",
|
|
4
|
+
"enforcement": "active",
|
|
5
|
+
"conditions": {
|
|
6
|
+
"ref_name": {
|
|
7
|
+
"exclude": [],
|
|
8
|
+
"include": [
|
|
9
|
+
"~DEFAULT_BRANCH",
|
|
10
|
+
"refs/heads/dev",
|
|
11
|
+
"refs/heads/staging",
|
|
12
|
+
"refs/heads/main"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"bypass_actors": [
|
|
17
|
+
{
|
|
18
|
+
"actor_id": null,
|
|
19
|
+
"actor_type": "DeployKey",
|
|
20
|
+
"bypass_mode": "always"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"actor_id": 5,
|
|
24
|
+
"actor_type": "RepositoryRole",
|
|
25
|
+
"bypass_mode": "always"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"rules": [
|
|
29
|
+
{
|
|
30
|
+
"type": "required_status_checks",
|
|
31
|
+
"parameters": {
|
|
32
|
+
"strict_required_status_checks_policy": false,
|
|
33
|
+
"do_not_enforce_on_create": true,
|
|
34
|
+
"required_status_checks": [
|
|
35
|
+
{
|
|
36
|
+
"context": "🔍 Quality Checks / 🧹 Lint",
|
|
37
|
+
"integration_id": 15368
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"context": "🔍 Quality Checks / 🔍 Type Check",
|
|
41
|
+
"integration_id": 15368
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"context": "🔍 Quality Checks / 🏗️ Build",
|
|
45
|
+
"integration_id": 15368
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"context": "🔍 Quality Checks / 📐 Check Formatting",
|
|
49
|
+
"integration_id": 15368
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"context": "🔍 Quality Checks / 🔒 Security Scan",
|
|
53
|
+
"integration_id": 15368
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"context": "🔍 Quality Checks / 🧪 Run Unit Tests",
|
|
57
|
+
"integration_id": 15368
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"context": "🔍 Quality Checks / 🧪 Run Integration Tests",
|
|
61
|
+
"integration_id": 15368
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|