@codyswann/lisa 2.198.1 → 2.199.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/cdk/create-only/.github/workflows/deploy.yml +27 -3
- package/expo/create-only/.github/workflows/deploy.yml +27 -3
- package/nestjs/create-only/.github/workflows/deploy.yml +26 -3
- package/package.json +1 -1
- 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 +58 -3
- package/plugins/lisa/commands/lisa/setup/github-repo.md +1 -1
- package/plugins/lisa/rules/reference/config-resolution.md +6 -0
- package/plugins/lisa/skills/lisa-setup-github-repo/SKILL.md +59 -4
- package/plugins/lisa-agy/commands/lisa/setup/github-repo.md +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-setup-github-repo/SKILL.md +59 -4
- 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 +1 -1
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +6 -0
- package/plugins/lisa-copilot/skills/lisa-setup-github-repo/SKILL.md +59 -4
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/commands/lisa/setup/github-repo.md +1 -1
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +6 -0
- package/plugins/lisa-cursor/skills/lisa-setup-github-repo/SKILL.md +59 -4
- 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 +1 -1
- package/plugins/src/base/rules/reference/config-resolution.md +6 -0
- package/plugins/src/base/skills/lisa-setup-github-repo/SKILL.md +59 -4
- package/scripts/lisa-github-environments.sh +256 -0
- package/scripts/lisa-github-repo-setup.sh +11 -4
- package/typescript/copy-overwrite/.github/GITHUB_ACTIONS.md +30 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lisa-setup-github-repo
|
|
3
|
-
description: "Apply Lisa's GitHub repository governance baseline to the current project's repo: repository settings (merge-only, auto-merge, delete-branch-on-merge, update-branch suggestions, wiki off, secret scanning where available), branch + tag rulesets from Lisa templates (base PR gate with CodeRabbit/GitGuardian/Quality Checks, prevent delete, protect tags, stack overlays),
|
|
4
|
-
allowed-tools: ["Bash", "Read", "AskUserQuestion"]
|
|
3
|
+
description: "Apply Lisa's GitHub repository governance baseline to the current project's repo: repository settings (merge-only, auto-merge, delete-branch-on-merge, update-branch suggestions, wiki off, secret scanning where available), branch + tag rulesets from Lisa templates (base PR gate with CodeRabbit/GitGuardian/Quality Checks, prevent delete, protect tags, stack overlays), a write-access deploy key + DEPLOY_KEY secret so release workflows can push version bumps through the rulesets' DeployKey bypass, and optional deployment environments with human-approval gates (required reviewers) from the github.environments block in .lisa.config.json. Idempotent — re-running updates settings, rulesets, and environments in place and skips an already-configured deploy key."
|
|
4
|
+
allowed-tools: ["Bash", "Read", "Edit", "AskUserQuestion"]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Setup GitHub Repository Governance
|
|
@@ -29,6 +29,28 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
|
|
|
29
29
|
- `prevent delete`, `protect tags` (`v*`), plus stack overlays (`cdk validation`, staging-only `playwright`)
|
|
30
30
|
- Repos without `.github/workflows/` get only app-based required checks — an Actions check that can never report would block every PR forever
|
|
31
31
|
3. **Deploy key** (`scripts/setup-deploy-key.sh --yes`) — write-access deploy key + `DEPLOY_KEY` secret, skipped when already configured. The `base` ruleset's `DeployKey: always` bypass is what lets CI version-bump pushes through protected branches.
|
|
32
|
+
4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"github": {
|
|
36
|
+
"environments": {
|
|
37
|
+
"production": {
|
|
38
|
+
"branch": "main",
|
|
39
|
+
"require_approval": true,
|
|
40
|
+
"reviewers": ["some-user", "some-org/some-team"],
|
|
41
|
+
"prevent_self_review": false,
|
|
42
|
+
"wait_timer": 0
|
|
43
|
+
},
|
|
44
|
+
"staging": { "branch": "staging" }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
- Environment names are friendly names (`production`), mapped to branches. Branch resolution order: explicit `branch` → `deploy.branches[<name>]` → the name itself.
|
|
50
|
+
- `require_approval: true` provisions **required reviewers** (usernames or `org/team-slug`, max 6) — GitHub pauses any workflow job bound to the environment until a listed reviewer approves it in the Actions UI. Reviewers are mandatory when `require_approval` is true; the script refuses a gate nobody can approve.
|
|
51
|
+
- Every declared environment also gets a **deployment branch policy** pinned to its branch, so only that branch can deploy to it.
|
|
52
|
+
- Provisioning matters: GitHub silently auto-creates an environment **without** protection rules the first time a workflow references it, so an unprovisioned approval gate gates on nothing.
|
|
53
|
+
- The stack `deploy.yml` templates read this same config at runtime and pass `require_approval`/`approval_environment` to `release.yml`, whose `release_approval` job is where the run pauses. Requires a public repo or a paid plan for private repos (the script skips gracefully otherwise).
|
|
32
54
|
|
|
33
55
|
## Workflow
|
|
34
56
|
|
|
@@ -56,7 +78,31 @@ bash "$LISA_SCRIPTS/lisa-github-repo-setup.sh" .
|
|
|
56
78
|
|
|
57
79
|
Requires `gh` authenticated with **admin** permission on the repo. A 403 on rulesets means the plan doesn't support them (private repo on a free personal plan) — settings and deploy key still apply; the script skips rulesets gracefully.
|
|
58
80
|
|
|
59
|
-
### Step 4 —
|
|
81
|
+
### Step 4 — Wire the approval gate into an existing deploy.yml (guided edit)
|
|
82
|
+
|
|
83
|
+
Only when `.lisa.config.json` has a `github.environments` entry with `require_approval: true` AND `.github/workflows/deploy.yml` exists but does not reference `approval_environment`:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
[ -f .github/workflows/deploy.yml ] \
|
|
87
|
+
&& jq -e '[.github.environments // {} | .[] | select(.require_approval == true)] | length > 0' .lisa.config.json > /dev/null 2>&1 \
|
|
88
|
+
&& ! grep -q 'approval_environment' .github/workflows/deploy.yml \
|
|
89
|
+
&& echo "deploy.yml needs approval wiring"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
A `grep` hit is only a first pass — before skipping the edit, read the `release` job's `with:` block and confirm it actually passes both `require_approval` and `approval_environment` from the `determine_environment` outputs (a commented-out or unrelated occurrence doesn't count as wired).
|
|
93
|
+
|
|
94
|
+
`deploy.yml` is create-only — the project owns it, so Lisa never patches it automatically. Show the user the two changes from the current stack template (`<stack>/create-only/.github/workflows/deploy.yml` in the Lisa repo) and offer to apply them via Edit:
|
|
95
|
+
|
|
96
|
+
1. In `determine_environment`, add a checkout step plus the `🚦 Resolve approval gate from .lisa.config.json` step, and expose the `approval_environment` / `require_approval` outputs.
|
|
97
|
+
2. In the `release` job's `with:` block, replace `require_approval: false` with:
|
|
98
|
+
```yaml
|
|
99
|
+
require_approval: ${{ needs.determine_environment.outputs.require_approval == 'true' }}
|
|
100
|
+
approval_environment: ${{ needs.determine_environment.outputs.approval_environment }}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Skip this step (and say why) if the project's deploy.yml doesn't call Lisa's `release.yml` (rails uses `release-rails.yml` and harper-fabric has no release call — approval gating for those stacks is not wired yet).
|
|
104
|
+
|
|
105
|
+
### Step 5 — Verify
|
|
60
106
|
|
|
61
107
|
```bash
|
|
62
108
|
gh api "repos/$(gh repo view --json nameWithOwner -q .nameWithOwner)" \
|
|
@@ -64,4 +110,13 @@ gh api "repos/$(gh repo view --json nameWithOwner -q .nameWithOwner)" \
|
|
|
64
110
|
gh api "repos/$(gh repo view --json nameWithOwner -q .nameWithOwner)/rulesets" --jq '[.[].name]'
|
|
65
111
|
```
|
|
66
112
|
|
|
67
|
-
|
|
113
|
+
When environments were configured, also confirm each one carries its protection rules and branch policy:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
gh api "repos/$(gh repo view --json nameWithOwner -q .nameWithOwner)/environments" \
|
|
117
|
+
--jq '.environments[] | {name, protection_rules, deployment_branch_policy}'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For every environment declared in `github.environments`, treat a missing `deployment_branch_policy` — or, when `require_approval` is true, an empty `protection_rules` — as a failure: the environment exists but is unprotected, so an approval gate bound to it would block nothing.
|
|
121
|
+
|
|
122
|
+
Report the applied settings, ruleset names, and environments. If any step failed, surface the error — do not mark the setup complete.
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# lisa-github-environments.sh
|
|
4
|
+
#
|
|
5
|
+
# Provisions GitHub deployment Environments declared in .lisa.config.json:
|
|
6
|
+
# { "github": { "environments": {
|
|
7
|
+
# "production": {
|
|
8
|
+
# "branch": "main",
|
|
9
|
+
# "require_approval": true,
|
|
10
|
+
# "reviewers": ["some-user", "some-org/some-team"],
|
|
11
|
+
# "prevent_self_review": false,
|
|
12
|
+
# "wait_timer": 0
|
|
13
|
+
# } } } }
|
|
14
|
+
#
|
|
15
|
+
# For each declared environment it applies:
|
|
16
|
+
# - required reviewers (the human approval gate), resolved from usernames
|
|
17
|
+
# ("login") and team slugs ("org/team-slug") to reviewer ids
|
|
18
|
+
# - prevent_self_review / wait_timer protection rules
|
|
19
|
+
# - a custom deployment branch policy pinned to the environment's branch,
|
|
20
|
+
# so only that branch can deploy to the environment
|
|
21
|
+
#
|
|
22
|
+
# Branch resolution order: .branch → deploy.branches[<name>] → <name> itself.
|
|
23
|
+
#
|
|
24
|
+
# Entirely optional: repos without github.environments in .lisa.config.json
|
|
25
|
+
# are left untouched. Provisioning matters because GitHub silently
|
|
26
|
+
# auto-creates an environment WITHOUT protection rules the first time a
|
|
27
|
+
# workflow references it — an approval gate bound to a non-provisioned
|
|
28
|
+
# environment gates on nothing.
|
|
29
|
+
#
|
|
30
|
+
# Usage:
|
|
31
|
+
# lisa-github-environments.sh [options] [project-path]
|
|
32
|
+
#
|
|
33
|
+
# Options:
|
|
34
|
+
# -n, --dry-run Show the environment payloads without applying them
|
|
35
|
+
# -v, --verbose Show detailed output
|
|
36
|
+
# -h, --help Show this help message
|
|
37
|
+
#
|
|
38
|
+
# Requires:
|
|
39
|
+
# - gh CLI (authenticated with repo admin permissions)
|
|
40
|
+
# - jq
|
|
41
|
+
#
|
|
42
|
+
|
|
43
|
+
set -eo pipefail
|
|
44
|
+
|
|
45
|
+
RED='\033[0;31m'
|
|
46
|
+
GREEN='\033[0;32m'
|
|
47
|
+
YELLOW='\033[0;33m'
|
|
48
|
+
BLUE='\033[0;34m'
|
|
49
|
+
NC='\033[0m'
|
|
50
|
+
|
|
51
|
+
DRY_RUN=false
|
|
52
|
+
VERBOSE=false
|
|
53
|
+
PROJECT_PATH=""
|
|
54
|
+
REPO=""
|
|
55
|
+
|
|
56
|
+
log_info() { echo -e "${BLUE}ℹ${NC} $1"; }
|
|
57
|
+
log_success() { echo -e "${GREEN}✓${NC} $1"; }
|
|
58
|
+
log_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
|
|
59
|
+
log_error() { echo -e "${RED}✗${NC} $1" >&2; }
|
|
60
|
+
log_verbose() { [[ "$VERBOSE" == "true" ]] && echo -e " $1" || true; }
|
|
61
|
+
|
|
62
|
+
show_help() {
|
|
63
|
+
sed -n '2,38p' "$0" | sed 's/^# \{0,1\}//'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
read_environments() {
|
|
67
|
+
local project_path="$1"
|
|
68
|
+
local envs="{}"
|
|
69
|
+
|
|
70
|
+
if [[ -f "$project_path/.lisa.config.json" ]]; then
|
|
71
|
+
if ! envs=$(jq '.github.environments // {}' "$project_path/.lisa.config.json" 2>/dev/null); then
|
|
72
|
+
log_warning ".lisa.config.json could not be parsed — ignoring github.environments" >&2
|
|
73
|
+
envs="{}"
|
|
74
|
+
fi
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
echo "$envs"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
resolve_branch() {
|
|
81
|
+
local project_path="$1"
|
|
82
|
+
local name="$2"
|
|
83
|
+
local env_json="$3"
|
|
84
|
+
local branch
|
|
85
|
+
|
|
86
|
+
branch=$(echo "$env_json" | jq -r '.branch // empty')
|
|
87
|
+
if [[ -z "$branch" && -f "$project_path/.lisa.config.json" ]]; then
|
|
88
|
+
branch=$(jq -r --arg n "$name" '.deploy.branches[$n] // empty' "$project_path/.lisa.config.json" 2>/dev/null) || branch=""
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
echo "${branch:-$name}"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# Resolves a reviewer entry to the {type, id} shape the environments API
|
|
95
|
+
# expects. Entries containing "/" are org team slugs; anything else is a user.
|
|
96
|
+
resolve_reviewer() {
|
|
97
|
+
local entry="$1"
|
|
98
|
+
local id
|
|
99
|
+
|
|
100
|
+
if [[ "$entry" == */* ]]; then
|
|
101
|
+
local org="${entry%%/*}"
|
|
102
|
+
local slug="${entry#*/}"
|
|
103
|
+
if ! id=$(gh api "orgs/$org/teams/$slug" --jq .id 2>/dev/null); then
|
|
104
|
+
log_error "Could not resolve team reviewer '$entry' — check the org/team-slug and your token's read:org scope"
|
|
105
|
+
return 1
|
|
106
|
+
fi
|
|
107
|
+
jq -n --argjson id "$id" '{type: "Team", id: $id}'
|
|
108
|
+
else
|
|
109
|
+
if ! id=$(gh api "users/$entry" --jq .id 2>/dev/null); then
|
|
110
|
+
log_error "Could not resolve user reviewer '$entry' — check the username"
|
|
111
|
+
return 1
|
|
112
|
+
fi
|
|
113
|
+
jq -n --argjson id "$id" '{type: "User", id: $id}'
|
|
114
|
+
fi
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
build_payload() {
|
|
118
|
+
local env_json="$1"
|
|
119
|
+
local reviewers_json="$2"
|
|
120
|
+
|
|
121
|
+
jq -n \
|
|
122
|
+
--argjson reviewers "$reviewers_json" \
|
|
123
|
+
--argjson prevent "$(echo "$env_json" | jq '.prevent_self_review // false')" \
|
|
124
|
+
--argjson wait "$(echo "$env_json" | jq '.wait_timer // 0')" \
|
|
125
|
+
'{
|
|
126
|
+
wait_timer: $wait,
|
|
127
|
+
prevent_self_review: $prevent,
|
|
128
|
+
reviewers: $reviewers,
|
|
129
|
+
deployment_branch_policy: {
|
|
130
|
+
protected_branches: false,
|
|
131
|
+
custom_branch_policies: true
|
|
132
|
+
}
|
|
133
|
+
}'
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
ensure_branch_policy() {
|
|
137
|
+
local name="$1"
|
|
138
|
+
local branch="$2"
|
|
139
|
+
local existing
|
|
140
|
+
|
|
141
|
+
existing=$(gh api "repos/$REPO/environments/$name/deployment-branch-policies" --jq '.branch_policies[].name' 2>/dev/null) || existing=""
|
|
142
|
+
if echo "$existing" | grep -qxF "$branch"; then
|
|
143
|
+
log_verbose "Deployment branch policy '$branch' already present on '$name'"
|
|
144
|
+
return 0
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
if jq -n --arg b "$branch" '{name: $b}' | gh api -X POST "repos/$REPO/environments/$name/deployment-branch-policies" --input - > /dev/null 2>&1; then
|
|
148
|
+
log_success "Pinned '$name' deployments to branch '$branch'"
|
|
149
|
+
else
|
|
150
|
+
log_warning "Could not create deployment branch policy '$branch' on '$name'"
|
|
151
|
+
fi
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
provision_environment() {
|
|
155
|
+
local name="$1"
|
|
156
|
+
local env_json="$2"
|
|
157
|
+
local branch
|
|
158
|
+
branch=$(resolve_branch "$PROJECT_PATH" "$name" "$env_json")
|
|
159
|
+
|
|
160
|
+
local require_approval
|
|
161
|
+
require_approval=$(echo "$env_json" | jq -r '.require_approval // false')
|
|
162
|
+
|
|
163
|
+
local reviewer_entries
|
|
164
|
+
reviewer_entries=$(echo "$env_json" | jq -r '.reviewers // [] | .[]')
|
|
165
|
+
|
|
166
|
+
# An approval gate with nobody able to approve would silently gate on
|
|
167
|
+
# nothing — refuse rather than provision a no-op.
|
|
168
|
+
if [[ "$require_approval" == "true" && -z "$reviewer_entries" ]]; then
|
|
169
|
+
log_error "Environment '$name' has require_approval: true but no reviewers — list at least one username or org/team-slug in github.environments.$name.reviewers"
|
|
170
|
+
exit 1
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
local reviewers_json="[]"
|
|
174
|
+
local entry reviewer
|
|
175
|
+
while IFS= read -r entry; do
|
|
176
|
+
[[ -z "$entry" ]] && continue
|
|
177
|
+
if ! reviewer=$(resolve_reviewer "$entry"); then
|
|
178
|
+
exit 1
|
|
179
|
+
fi
|
|
180
|
+
reviewers_json=$(echo "$reviewers_json" | jq --argjson r "$reviewer" '. + [$r]')
|
|
181
|
+
done <<< "$reviewer_entries"
|
|
182
|
+
|
|
183
|
+
local payload
|
|
184
|
+
payload=$(build_payload "$env_json" "$reviewers_json")
|
|
185
|
+
log_verbose "Environment '$name' payload: $(echo "$payload" | jq -c .)"
|
|
186
|
+
|
|
187
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
188
|
+
log_info "[DRY RUN] Would provision environment '$name' (branch: $branch):"
|
|
189
|
+
echo "$payload" | jq .
|
|
190
|
+
log_info "[DRY RUN] Would pin deployment branch policy '$branch' on '$name'"
|
|
191
|
+
return 0
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
local response
|
|
195
|
+
if ! response=$(echo "$payload" | gh api -X PUT "repos/$REPO/environments/$name" --input - 2>&1); then
|
|
196
|
+
# Environments with protection rules need a public repo or a paid plan —
|
|
197
|
+
# skip gracefully like the rulesets script does.
|
|
198
|
+
if echo "$response" | grep -qiE "HTTP 403|HTTP 422|upgrade to github"; then
|
|
199
|
+
log_warning "Environments are not available on this repository's plan — skipped '$name'. Approval gates will not be enforced until the repo is public or on a paid plan."
|
|
200
|
+
return 0
|
|
201
|
+
fi
|
|
202
|
+
log_error "Failed to provision environment '$name': $response"
|
|
203
|
+
exit 1
|
|
204
|
+
fi
|
|
205
|
+
log_success "Provisioned environment '$name' (branch: $branch)"
|
|
206
|
+
|
|
207
|
+
ensure_branch_policy "$name" "$branch"
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
main() {
|
|
211
|
+
while [[ $# -gt 0 ]]; do
|
|
212
|
+
case $1 in
|
|
213
|
+
-n|--dry-run) DRY_RUN=true; shift ;;
|
|
214
|
+
-v|--verbose) VERBOSE=true; shift ;;
|
|
215
|
+
-h|--help) show_help; exit 0 ;;
|
|
216
|
+
-*) log_error "Unknown option: $1"; show_help; exit 1 ;;
|
|
217
|
+
*) PROJECT_PATH="$1"; shift ;;
|
|
218
|
+
esac
|
|
219
|
+
done
|
|
220
|
+
|
|
221
|
+
PROJECT_PATH="${PROJECT_PATH:-.}"
|
|
222
|
+
PROJECT_PATH="$(cd "$PROJECT_PATH" && pwd)"
|
|
223
|
+
|
|
224
|
+
if ! command -v gh &> /dev/null || ! command -v jq &> /dev/null; then
|
|
225
|
+
log_error "Requires gh and jq"
|
|
226
|
+
exit 1
|
|
227
|
+
fi
|
|
228
|
+
|
|
229
|
+
local envs
|
|
230
|
+
envs=$(read_environments "$PROJECT_PATH")
|
|
231
|
+
if [[ "$(echo "$envs" | jq 'length')" == "0" ]]; then
|
|
232
|
+
log_info "No github.environments configured in .lisa.config.json — skipping"
|
|
233
|
+
return 0
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
if ! gh auth status &> /dev/null; then
|
|
237
|
+
log_error "GitHub CLI is not authenticated. Run 'gh auth login' first."
|
|
238
|
+
exit 1
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
cd "$PROJECT_PATH"
|
|
242
|
+
REPO=$(gh repo view --json nameWithOwner -q '.nameWithOwner' 2>/dev/null) || {
|
|
243
|
+
log_error "Could not determine repository for $PROJECT_PATH"
|
|
244
|
+
exit 1
|
|
245
|
+
}
|
|
246
|
+
log_info "Repository: $REPO"
|
|
247
|
+
|
|
248
|
+
local name env_json
|
|
249
|
+
while IFS= read -r name; do
|
|
250
|
+
[[ -z "$name" ]] && continue
|
|
251
|
+
env_json=$(echo "$envs" | jq --arg n "$name" '.[$n]')
|
|
252
|
+
provision_environment "$name" "$env_json"
|
|
253
|
+
done <<< "$(echo "$envs" | jq -r 'keys[]')"
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
main "$@"
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
# 3. CI deploy key + DEPLOY_KEY secret (setup-deploy-key.sh --yes),
|
|
9
9
|
# so release workflows can push version bumps through the rulesets'
|
|
10
10
|
# DeployKey bypass.
|
|
11
|
+
# 4. Deployment environments with required-reviewer approval gates
|
|
12
|
+
# (lisa-github-environments.sh), from the optional
|
|
13
|
+
# github.environments block in .lisa.config.json.
|
|
11
14
|
#
|
|
12
15
|
# Usage:
|
|
13
16
|
# lisa-github-repo-setup.sh [options] [project-path]
|
|
@@ -35,7 +38,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
35
38
|
case $1 in
|
|
36
39
|
-n|--dry-run) DRY_RUN=true; PASSTHROUGH+=("--dry-run"); shift ;;
|
|
37
40
|
-v|--verbose) VERBOSE=true; PASSTHROUGH+=("--verbose"); shift ;;
|
|
38
|
-
-h|--help) sed -n '2,
|
|
41
|
+
-h|--help) sed -n '2,27p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
39
42
|
-*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
40
43
|
*) PROJECT_PATH="$1"; shift ;;
|
|
41
44
|
esac
|
|
@@ -44,15 +47,15 @@ done
|
|
|
44
47
|
PROJECT_PATH="${PROJECT_PATH:-.}"
|
|
45
48
|
PROJECT_PATH="$(cd "$PROJECT_PATH" && pwd)"
|
|
46
49
|
|
|
47
|
-
echo "==> Step 1/
|
|
50
|
+
echo "==> Step 1/4: repository settings"
|
|
48
51
|
bash "$SCRIPT_DIR/lisa-github-repo-settings.sh" "${PASSTHROUGH[@]}" "$PROJECT_PATH"
|
|
49
52
|
|
|
50
53
|
echo ""
|
|
51
|
-
echo "==> Step 2/
|
|
54
|
+
echo "==> Step 2/4: rulesets"
|
|
52
55
|
bash "$SCRIPT_DIR/lisa-github-rulesets.sh" --yes "${PASSTHROUGH[@]}" "$PROJECT_PATH"
|
|
53
56
|
|
|
54
57
|
echo ""
|
|
55
|
-
echo "==> Step 3/
|
|
58
|
+
echo "==> Step 3/4: deploy key"
|
|
56
59
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
57
60
|
echo "[DRY RUN] Would ensure a write-access deploy key + DEPLOY_KEY secret exist"
|
|
58
61
|
else
|
|
@@ -69,5 +72,9 @@ else
|
|
|
69
72
|
fi
|
|
70
73
|
fi
|
|
71
74
|
|
|
75
|
+
echo ""
|
|
76
|
+
echo "==> Step 4/4: deployment environments"
|
|
77
|
+
bash "$SCRIPT_DIR/lisa-github-environments.sh" "${PASSTHROUGH[@]}" "$PROJECT_PATH"
|
|
78
|
+
|
|
72
79
|
echo ""
|
|
73
80
|
echo "✓ GitHub repository governance setup complete"
|
|
@@ -123,6 +123,31 @@ Enterprise-grade release management:
|
|
|
123
123
|
- Sentry release creation
|
|
124
124
|
- Jira release creation
|
|
125
125
|
- Compliance validation (SOC2, ISO27001, HIPAA, PCI-DSS)
|
|
126
|
+
- Optional human-approval gate (`require_approval` + `approval_environment`)
|
|
127
|
+
|
|
128
|
+
**Human-approval gate**: pass `require_approval: true` and the run pauses at the
|
|
129
|
+
`🚦 Release Approval` job until a required reviewer approves it in the Actions UI.
|
|
130
|
+
The pause is enforced by the GitHub Environment named in `approval_environment`
|
|
131
|
+
(falls back to `environment`, i.e. the branch name):
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
uses: CodySwannGT/lisa/.github/workflows/release.yml@main
|
|
135
|
+
with:
|
|
136
|
+
require_approval: true
|
|
137
|
+
approval_environment: 'production'
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Because everything downstream (versioning, GitHub Release, and any deploy job
|
|
141
|
+
that `needs: release`) chains off this gate, approval blocks the whole pipeline.
|
|
142
|
+
The Lisa stack `deploy.yml` templates wire both inputs automatically from the
|
|
143
|
+
optional `github.environments` block in `.lisa.config.json`, mapping the branch
|
|
144
|
+
to its friendly environment name (`main` → `production` via `deploy.branches`).
|
|
145
|
+
The environment itself — required reviewers and a deployment branch policy —
|
|
146
|
+
is provisioned by `/lisa:setup:github-repo`. Provision before enabling the gate:
|
|
147
|
+
GitHub auto-creates unprovisioned environments **without** protection rules, so
|
|
148
|
+
an unprovisioned gate blocks nothing. Not yet wired for rails
|
|
149
|
+
(`release-rails.yml` doesn't thread approval inputs) or harper-fabric (no
|
|
150
|
+
release.yml call).
|
|
126
151
|
|
|
127
152
|
**Blackout Periods** (configurable):
|
|
128
153
|
- Production: No weekends, no late nights (10 PM - 6 AM)
|
|
@@ -576,7 +601,11 @@ with:
|
|
|
576
601
|
approval_environment: 'production'
|
|
577
602
|
```
|
|
578
603
|
|
|
579
|
-
**Note**: Create the environment in **Settings** > **Environments** first
|
|
604
|
+
**Note**: Create the environment in **Settings** > **Environments** first (or
|
|
605
|
+
declare it under `github.environments` in `.lisa.config.json` and run
|
|
606
|
+
`/lisa:setup:github-repo`). Unlike release.yml's environment-enforced gate,
|
|
607
|
+
quality.yml's `approval_gate` job only records an audit artifact — it does not
|
|
608
|
+
pause the run. For an enforced human gate, use release.yml's `require_approval`.
|
|
580
609
|
|
|
581
610
|
### Custom Node Version
|
|
582
611
|
|