jekyll-theme-zer0 0.22.21 → 1.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +64 -0
- data/README.md +294 -23
- data/scripts/bin/install +717 -0
- data/scripts/bin/test +45 -2
- data/scripts/lib/changelog.sh +24 -17
- data/scripts/lib/install/README.md +63 -0
- data/scripts/lib/install/agents.sh +166 -0
- data/scripts/lib/install/ai/diagnose.sh +199 -0
- data/scripts/lib/install/ai/openai.sh +233 -0
- data/scripts/lib/install/ai/suggest.sh +182 -0
- data/scripts/lib/install/ai/wizard.sh +160 -0
- data/scripts/lib/install/config.sh +56 -0
- data/scripts/lib/install/deploy/README.md +52 -0
- data/scripts/lib/install/deploy/azure-swa.sh +50 -0
- data/scripts/lib/install/deploy/docker-prod.sh +71 -0
- data/scripts/lib/install/deploy/github-pages.sh +44 -0
- data/scripts/lib/install/deploy/registry.sh +190 -0
- data/scripts/lib/install/doctor.sh +301 -0
- data/scripts/lib/install/fs.sh +52 -0
- data/scripts/lib/install/logging.sh +33 -0
- data/scripts/lib/install/pages.sh +255 -0
- data/scripts/lib/install/platform.sh +71 -0
- data/scripts/lib/install/profile.sh +113 -0
- data/scripts/lib/install/template.sh +137 -0
- data/scripts/lib/install/upgrade.sh +184 -0
- data/scripts/lib/install/wizard_interactive.sh +189 -0
- data/scripts/test/lib/run_tests.sh +1 -0
- data/scripts/test/lib/test_analyze_commits.sh +109 -0
- data/scripts/utils/analyze-commits +37 -17
- metadata +25 -2
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/lib/install/upgrade.sh
|
|
3
|
+
#
|
|
4
|
+
# `install upgrade` — version-aware migration over an existing site.
|
|
5
|
+
#
|
|
6
|
+
# Strategy:
|
|
7
|
+
# 1. Detect installed version from .zer0-installed (drop-marker) or
|
|
8
|
+
# _config.yml's `version:` field, or fall back to "unknown".
|
|
9
|
+
# 2. Print a diff-summary of what will change (theme files only — never
|
|
10
|
+
# touches user content under pages/, _posts/, _drafts/).
|
|
11
|
+
# 3. Re-runs the agent-files install (always safe, additive) and offers
|
|
12
|
+
# to refresh templated workflows under .github/workflows/.
|
|
13
|
+
# 4. Writes a fresh .zer0-installed marker with the new version + date.
|
|
14
|
+
#
|
|
15
|
+
# Public API:
|
|
16
|
+
# upgrade_run <target_dir> <repo_root> [--from <version>] [--force]
|
|
17
|
+
# [--dry-run] [--auto-accept]
|
|
18
|
+
#
|
|
19
|
+
# Bash 3.2-compatible. Idempotent. Never destructive without explicit --force.
|
|
20
|
+
|
|
21
|
+
# shellcheck disable=SC2034
|
|
22
|
+
UPGRADE_LIB_VERSION="1.0.0"
|
|
23
|
+
|
|
24
|
+
UPGRADE_MARKER=".zer0-installed"
|
|
25
|
+
|
|
26
|
+
# Read theme version from lib/jekyll-theme-zer0/version.rb
|
|
27
|
+
_upgrade_theme_version() {
|
|
28
|
+
local repo_root="$1"
|
|
29
|
+
local vfile="$repo_root/lib/jekyll-theme-zer0/version.rb"
|
|
30
|
+
[[ -f "$vfile" ]] || { echo "unknown"; return; }
|
|
31
|
+
grep -E 'VERSION\s*=' "$vfile" | head -n1 | sed -E 's/.*"([^"]+)".*/\1/'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Detect the previously installed version (best-effort).
|
|
35
|
+
_upgrade_detect_installed() {
|
|
36
|
+
local target_dir="$1"
|
|
37
|
+
if [[ -f "$target_dir/$UPGRADE_MARKER" ]]; then
|
|
38
|
+
grep -E '^version:' "$target_dir/$UPGRADE_MARKER" 2>/dev/null \
|
|
39
|
+
| head -n1 | sed -E 's/version:[[:space:]]*//'
|
|
40
|
+
return
|
|
41
|
+
fi
|
|
42
|
+
# Fallback: probe _config.yml for a `version:` line
|
|
43
|
+
if [[ -f "$target_dir/_config.yml" ]]; then
|
|
44
|
+
local v
|
|
45
|
+
v="$(grep -E '^version:' "$target_dir/_config.yml" 2>/dev/null \
|
|
46
|
+
| head -n1 | sed -E 's/version:[[:space:]]*//' \
|
|
47
|
+
| tr -d '"' | tr -d "'")"
|
|
48
|
+
[[ -n "$v" ]] && { echo "$v"; return; }
|
|
49
|
+
fi
|
|
50
|
+
echo "unknown"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Write/update the install marker.
|
|
54
|
+
_upgrade_write_marker() {
|
|
55
|
+
local target_dir="$1" version="$2" dry_run="$3"
|
|
56
|
+
local marker="$target_dir/$UPGRADE_MARKER"
|
|
57
|
+
local content
|
|
58
|
+
content="$(cat <<EOF
|
|
59
|
+
# zer0-mistakes install marker — do not edit manually
|
|
60
|
+
version: $version
|
|
61
|
+
upgraded_at: $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
62
|
+
EOF
|
|
63
|
+
)"
|
|
64
|
+
if [[ "$dry_run" = "1" ]]; then
|
|
65
|
+
log_info "[dry-run] Would write marker: $marker"
|
|
66
|
+
return
|
|
67
|
+
fi
|
|
68
|
+
printf '%s\n' "$content" > "$marker"
|
|
69
|
+
log_success "Wrote $UPGRADE_MARKER (version: $version)"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Re-run agents install in --force mode (theme files are safe to refresh).
|
|
73
|
+
_upgrade_refresh_agents() {
|
|
74
|
+
local target_dir="$1" repo_root="$2" force="$3" dry_run="$4"
|
|
75
|
+
if ! declare -F agents_install >/dev/null 2>&1; then
|
|
76
|
+
log_warning "agents.sh not loaded — skipping agent-file refresh"
|
|
77
|
+
return 0
|
|
78
|
+
fi
|
|
79
|
+
if [[ "$dry_run" = "1" ]]; then
|
|
80
|
+
log_info "[dry-run] Would refresh agent files in $target_dir"
|
|
81
|
+
return 0
|
|
82
|
+
fi
|
|
83
|
+
local force_flag=""
|
|
84
|
+
[[ "$force" = "1" ]] && force_flag="--force"
|
|
85
|
+
log_info "Refreshing AI agent files..."
|
|
86
|
+
agents_install "$target_dir" "$repo_root" $force_flag || true
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Compare a workflow file in target_dir vs theme template — list
|
|
90
|
+
# differences so user can decide whether to merge.
|
|
91
|
+
_upgrade_check_workflows() {
|
|
92
|
+
local target_dir="$1" repo_root="$2"
|
|
93
|
+
local wf_dir="$target_dir/.github/workflows"
|
|
94
|
+
[[ -d "$wf_dir" ]] || return 0
|
|
95
|
+
local f base tpl
|
|
96
|
+
log_info "Checking .github/workflows/ for theme-managed files..."
|
|
97
|
+
local found=0
|
|
98
|
+
for f in "$wf_dir"/*.yml "$wf_dir"/*.yaml; do
|
|
99
|
+
[[ -f "$f" ]] || continue
|
|
100
|
+
base="$(basename "$f")"
|
|
101
|
+
# Look for a matching template under templates/deploy/*/
|
|
102
|
+
for tpl in "$repo_root/templates/deploy"/*/"$base.template" \
|
|
103
|
+
"$repo_root/templates/deploy"/*/"$base"; do
|
|
104
|
+
[[ -f "$tpl" ]] || continue
|
|
105
|
+
found=$((found+1))
|
|
106
|
+
if diff -q "$f" "$tpl" >/dev/null 2>&1; then
|
|
107
|
+
log_success " $base — up to date"
|
|
108
|
+
else
|
|
109
|
+
log_warning " $base — differs from theme template"
|
|
110
|
+
log_info " Compare: diff $f $tpl"
|
|
111
|
+
fi
|
|
112
|
+
break
|
|
113
|
+
done
|
|
114
|
+
done
|
|
115
|
+
[[ "$found" = "0" ]] && log_info " No theme-managed workflows detected"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Public entrypoint.
|
|
119
|
+
upgrade_run() {
|
|
120
|
+
local target_dir="$1" repo_root="$2"
|
|
121
|
+
shift 2 || true
|
|
122
|
+
|
|
123
|
+
local from_version="" force=0 dry_run=0 auto_accept=0
|
|
124
|
+
while [[ $# -gt 0 ]]; do
|
|
125
|
+
case "$1" in
|
|
126
|
+
--from) from_version="${2:-}"; shift ;;
|
|
127
|
+
-f|--force) force=1 ;;
|
|
128
|
+
-n|--dry-run) dry_run=1 ;;
|
|
129
|
+
--auto-accept) auto_accept=1 ;;
|
|
130
|
+
*) log_warning "upgrade_run: ignoring unknown flag: $1" ;;
|
|
131
|
+
esac
|
|
132
|
+
shift
|
|
133
|
+
done
|
|
134
|
+
|
|
135
|
+
if [[ ! -d "$target_dir" ]]; then
|
|
136
|
+
log_error "Target directory does not exist: $target_dir"
|
|
137
|
+
return 1
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
local installed_version theme_version
|
|
141
|
+
installed_version="${from_version:-$(_upgrade_detect_installed "$target_dir")}"
|
|
142
|
+
theme_version="$(_upgrade_theme_version "$repo_root")"
|
|
143
|
+
|
|
144
|
+
log_info "🔧 Upgrading site at: $target_dir"
|
|
145
|
+
log_info " From version: $installed_version"
|
|
146
|
+
log_info " To version: $theme_version"
|
|
147
|
+
[[ "$dry_run" = "1" ]] && log_warning " Mode: dry-run (no files will be changed)"
|
|
148
|
+
echo
|
|
149
|
+
|
|
150
|
+
if [[ "$installed_version" = "$theme_version" ]] && [[ "$force" != "1" ]]; then
|
|
151
|
+
log_success "Already on $theme_version. Use --force to re-run anyway."
|
|
152
|
+
return 0
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# Confirmation gate (skipped when --auto-accept or --dry-run)
|
|
156
|
+
if [[ "$auto_accept" != "1" ]] && [[ "$dry_run" != "1" ]]; then
|
|
157
|
+
printf "Proceed with upgrade? [y/N] "
|
|
158
|
+
local reply
|
|
159
|
+
read -r reply || reply=""
|
|
160
|
+
case "$reply" in
|
|
161
|
+
y|Y|yes|YES) ;;
|
|
162
|
+
*) log_warning "Upgrade cancelled by user."; return 0 ;;
|
|
163
|
+
esac
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# 1. Refresh agent files (always additive/safe)
|
|
167
|
+
_upgrade_refresh_agents "$target_dir" "$repo_root" "$force" "$dry_run"
|
|
168
|
+
echo
|
|
169
|
+
|
|
170
|
+
# 2. Check workflows (read-only — never auto-overwrite)
|
|
171
|
+
_upgrade_check_workflows "$target_dir" "$repo_root"
|
|
172
|
+
echo
|
|
173
|
+
|
|
174
|
+
# 3. Write/update marker (only if not dry-run)
|
|
175
|
+
_upgrade_write_marker "$target_dir" "$theme_version" "$dry_run"
|
|
176
|
+
echo
|
|
177
|
+
|
|
178
|
+
log_success "Upgrade complete."
|
|
179
|
+
log_info "Next steps:"
|
|
180
|
+
log_info " 1. Run 'install doctor' to verify environment"
|
|
181
|
+
log_info " 2. Review any workflow files flagged above"
|
|
182
|
+
log_info " 3. Check CHANGELOG.md in the theme repo for breaking changes"
|
|
183
|
+
return 0
|
|
184
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/lib/install/wizard_interactive.sh
|
|
3
|
+
#
|
|
4
|
+
# Non-AI interactive wizard. Prompts the user for site config + profile,
|
|
5
|
+
# then dispatches to `install init` with the chosen options.
|
|
6
|
+
#
|
|
7
|
+
# Complementary to ai/wizard.sh (which uses OpenAI). This one is fully
|
|
8
|
+
# offline and works in any environment.
|
|
9
|
+
#
|
|
10
|
+
# Public API:
|
|
11
|
+
# wizard_interactive_run <target_dir> <repo_root> [--auto-accept]
|
|
12
|
+
#
|
|
13
|
+
# Bash 3.2-compatible.
|
|
14
|
+
|
|
15
|
+
# shellcheck disable=SC2034
|
|
16
|
+
WIZARD_INTERACTIVE_LIB_VERSION="1.0.0"
|
|
17
|
+
|
|
18
|
+
# Prompt with a default value. Returns the user's input on stdout.
|
|
19
|
+
# args: prompt default
|
|
20
|
+
_wiz_prompt() {
|
|
21
|
+
local prompt="$1" default="$2" reply
|
|
22
|
+
if [[ -n "$default" ]]; then
|
|
23
|
+
printf " %s [%s]: " "$prompt" "$default" >&2
|
|
24
|
+
else
|
|
25
|
+
printf " %s: " "$prompt" >&2
|
|
26
|
+
fi
|
|
27
|
+
read -r reply || reply=""
|
|
28
|
+
[[ -z "$reply" ]] && reply="$default"
|
|
29
|
+
printf '%s\n' "$reply"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Prompt for a yes/no answer. Returns 0 for yes, 1 for no.
|
|
33
|
+
# args: prompt default(y|n)
|
|
34
|
+
_wiz_confirm() {
|
|
35
|
+
local prompt="$1" default="${2:-n}" reply hint
|
|
36
|
+
case "$default" in
|
|
37
|
+
y|Y) hint="[Y/n]" ;;
|
|
38
|
+
*) hint="[y/N]" ;;
|
|
39
|
+
esac
|
|
40
|
+
printf " %s %s: " "$prompt" "$hint" >&2
|
|
41
|
+
read -r reply || reply=""
|
|
42
|
+
[[ -z "$reply" ]] && reply="$default"
|
|
43
|
+
case "$reply" in
|
|
44
|
+
y|Y|yes|YES) return 0 ;;
|
|
45
|
+
*) return 1 ;;
|
|
46
|
+
esac
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Pick from a numbered list. Returns the chosen value on stdout.
|
|
50
|
+
# args: prompt default option1 option2 ...
|
|
51
|
+
_wiz_choose() {
|
|
52
|
+
local prompt="$1" default="$2"
|
|
53
|
+
shift 2
|
|
54
|
+
local options=("$@")
|
|
55
|
+
local i=1 opt
|
|
56
|
+
echo " $prompt" >&2
|
|
57
|
+
for opt in "${options[@]}"; do
|
|
58
|
+
if [[ "$opt" = "$default" ]]; then
|
|
59
|
+
printf " %d) %s (default)\n" "$i" "$opt" >&2
|
|
60
|
+
else
|
|
61
|
+
printf " %d) %s\n" "$i" "$opt" >&2
|
|
62
|
+
fi
|
|
63
|
+
i=$((i+1))
|
|
64
|
+
done
|
|
65
|
+
printf " Choice [%s]: " "$default" >&2
|
|
66
|
+
local reply
|
|
67
|
+
read -r reply || reply=""
|
|
68
|
+
[[ -z "$reply" ]] && { printf '%s\n' "$default"; return; }
|
|
69
|
+
# Numeric or by-name
|
|
70
|
+
case "$reply" in
|
|
71
|
+
''|*[!0-9]*)
|
|
72
|
+
# by name; verify it exists
|
|
73
|
+
for opt in "${options[@]}"; do
|
|
74
|
+
[[ "$opt" = "$reply" ]] && { printf '%s\n' "$reply"; return; }
|
|
75
|
+
done
|
|
76
|
+
printf '%s\n' "$default" ;;
|
|
77
|
+
*)
|
|
78
|
+
local idx=$((reply))
|
|
79
|
+
if (( idx >= 1 && idx <= ${#options[@]} )); then
|
|
80
|
+
printf '%s\n' "${options[$((idx-1))]}"
|
|
81
|
+
else
|
|
82
|
+
printf '%s\n' "$default"
|
|
83
|
+
fi ;;
|
|
84
|
+
esac
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Public entrypoint.
|
|
88
|
+
wizard_interactive_run() {
|
|
89
|
+
local target_dir="$1" repo_root="$2"
|
|
90
|
+
shift 2 || true
|
|
91
|
+
local auto_accept=0
|
|
92
|
+
while [[ $# -gt 0 ]]; do
|
|
93
|
+
case "$1" in
|
|
94
|
+
--auto-accept) auto_accept=1 ;;
|
|
95
|
+
*) log_warning "wizard_interactive_run: ignoring unknown flag: $1" ;;
|
|
96
|
+
esac
|
|
97
|
+
shift
|
|
98
|
+
done
|
|
99
|
+
|
|
100
|
+
log_info "🧙 Interactive wizard (non-AI)"
|
|
101
|
+
log_info " This wizard collects site settings and runs 'install init'."
|
|
102
|
+
log_info " Press Enter to accept defaults shown in [brackets]."
|
|
103
|
+
echo
|
|
104
|
+
|
|
105
|
+
# Discover available profiles dynamically if loader is present.
|
|
106
|
+
local default_profile="full"
|
|
107
|
+
local profiles_csv="minimal full fork github remote"
|
|
108
|
+
if declare -F list_profile_names >/dev/null 2>&1; then
|
|
109
|
+
local discovered
|
|
110
|
+
discovered="$(list_profile_names "$repo_root" 2>/dev/null | tr '\n' ' ')"
|
|
111
|
+
[[ -n "$discovered" ]] && profiles_csv="$discovered"
|
|
112
|
+
fi
|
|
113
|
+
# shellcheck disable=SC2206
|
|
114
|
+
local profile_options=( $profiles_csv )
|
|
115
|
+
|
|
116
|
+
log_info "── Site basics ──────────────────────────"
|
|
117
|
+
local site_name site_desc author email
|
|
118
|
+
site_name="$(_wiz_prompt "Site name" "$(basename "$target_dir")")"
|
|
119
|
+
site_desc="$(_wiz_prompt "Description" "A Jekyll site powered by zer0-mistakes")"
|
|
120
|
+
author="$(_wiz_prompt "Author name" "${USER:-author}")"
|
|
121
|
+
email="$(_wiz_prompt "Author email" "${USER:-author}@example.com")"
|
|
122
|
+
echo
|
|
123
|
+
|
|
124
|
+
log_info "── Profile ──────────────────────────────"
|
|
125
|
+
local profile
|
|
126
|
+
profile="$(_wiz_choose "Choose an install profile:" "$default_profile" "${profile_options[@]}")"
|
|
127
|
+
echo
|
|
128
|
+
|
|
129
|
+
log_info "── Optional integrations ────────────────"
|
|
130
|
+
local want_agents=0 want_docker=0
|
|
131
|
+
_wiz_confirm "Install AI agent guidance files?" "y" && want_agents=1
|
|
132
|
+
_wiz_confirm "Add docker-prod deploy target?" "n" && want_docker=1
|
|
133
|
+
echo
|
|
134
|
+
|
|
135
|
+
log_info "── Summary ──────────────────────────────"
|
|
136
|
+
log_info " Target dir: $target_dir"
|
|
137
|
+
log_info " Site name: $site_name"
|
|
138
|
+
log_info " Description: $site_desc"
|
|
139
|
+
log_info " Author: $author <$email>"
|
|
140
|
+
log_info " Profile: $profile"
|
|
141
|
+
log_info " Agents: $([ "$want_agents" = "1" ] && echo yes || echo no)"
|
|
142
|
+
log_info " docker-prod: $([ "$want_docker" = "1" ] && echo yes || echo no)"
|
|
143
|
+
echo
|
|
144
|
+
|
|
145
|
+
if [[ "$auto_accept" != "1" ]]; then
|
|
146
|
+
if ! _wiz_confirm "Proceed with these settings?" "y"; then
|
|
147
|
+
log_warning "Wizard cancelled."
|
|
148
|
+
return 0
|
|
149
|
+
fi
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# Export for any downstream consumers (legacy install.sh reads SITE_NAME etc.)
|
|
153
|
+
export SITE_NAME="$site_name"
|
|
154
|
+
export SITE_DESCRIPTION="$site_desc"
|
|
155
|
+
export AUTHOR_NAME="$author"
|
|
156
|
+
export AUTHOR_EMAIL="$email"
|
|
157
|
+
|
|
158
|
+
# Dispatch
|
|
159
|
+
local install_bin="$repo_root/scripts/bin/install"
|
|
160
|
+
if [[ ! -x "$install_bin" ]]; then
|
|
161
|
+
log_error "Cannot find $install_bin"
|
|
162
|
+
return 1
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
log_info "Running: install init --profile $profile $target_dir"
|
|
166
|
+
"$install_bin" init --profile "$profile" --skip-doctor "$target_dir" || {
|
|
167
|
+
log_error "init failed"
|
|
168
|
+
return 1
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if [[ "$want_agents" = "1" ]]; then
|
|
172
|
+
echo
|
|
173
|
+
log_info "Running: install agents $target_dir"
|
|
174
|
+
"$install_bin" agents "$target_dir" || log_warning "agents install reported issues"
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
if [[ "$want_docker" = "1" ]]; then
|
|
178
|
+
echo
|
|
179
|
+
log_info "Running: install deploy docker-prod $target_dir"
|
|
180
|
+
"$install_bin" deploy docker-prod "$target_dir" || log_warning "docker-prod install reported issues"
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
echo
|
|
184
|
+
log_success "Wizard complete!"
|
|
185
|
+
log_info "Next steps:"
|
|
186
|
+
log_info " cd $target_dir"
|
|
187
|
+
log_info " docker compose up # or: bundle exec jekyll serve"
|
|
188
|
+
return 0
|
|
189
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Unit tests for scripts/utils/analyze-commits
|
|
4
|
+
#
|
|
5
|
+
# These tests build a throwaway git repo per case so we can verify that:
|
|
6
|
+
# 1. The analyzer never crashes on its own log calls (regression test for
|
|
7
|
+
# the silent log_info bug that caused a v1.0.0 release to be published
|
|
8
|
+
# as a patch in v0.22.22).
|
|
9
|
+
# 2. Conventional Commits "!" breaking-change marker triggers a major bump.
|
|
10
|
+
# 3. Scoped types like `feat(scope):` are still recognised.
|
|
11
|
+
# 4. The analyzer writes ONLY the bump type to stdout (logs go to stderr),
|
|
12
|
+
# so callers like the version-bump workflow can rely on
|
|
13
|
+
# `BUMP=$(./analyze-commits ...)`.
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
ANALYZER="$(cd "$SCRIPT_DIR/../../utils" && pwd)/analyze-commits"
|
|
17
|
+
|
|
18
|
+
set +e
|
|
19
|
+
|
|
20
|
+
print_suite_header "analyze-commits"
|
|
21
|
+
|
|
22
|
+
# Helper: run analyzer in an isolated git repo containing one commit with the
|
|
23
|
+
# given subject (and optional body). Echoes the bump type from stdout only.
|
|
24
|
+
_analyzer_for_commit() {
|
|
25
|
+
local subject="$1"
|
|
26
|
+
local body="${2:-}"
|
|
27
|
+
|
|
28
|
+
local tmp
|
|
29
|
+
tmp=$(mktemp -d)
|
|
30
|
+
(
|
|
31
|
+
cd "$tmp" || exit 1
|
|
32
|
+
git init -q -b main 2>/dev/null || git init -q
|
|
33
|
+
git config user.email "test@example.com"
|
|
34
|
+
git config user.name "Test"
|
|
35
|
+
git config commit.gpgsign false
|
|
36
|
+
echo "seed" > seed.txt
|
|
37
|
+
git add seed.txt
|
|
38
|
+
git commit -q -m "chore: seed"
|
|
39
|
+
echo "change" > change.txt
|
|
40
|
+
git add change.txt
|
|
41
|
+
if [[ -n "$body" ]]; then
|
|
42
|
+
git commit -q -m "$subject" -m "$body"
|
|
43
|
+
else
|
|
44
|
+
git commit -q -m "$subject"
|
|
45
|
+
fi
|
|
46
|
+
# Run analyzer; capture stdout only (stderr goes to caller for debugging on failure)
|
|
47
|
+
"$ANALYZER" HEAD~1..HEAD 2>/dev/null
|
|
48
|
+
)
|
|
49
|
+
local rc=$?
|
|
50
|
+
rm -rf "$tmp"
|
|
51
|
+
return $rc
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_run_case() {
|
|
55
|
+
local subject="$1"
|
|
56
|
+
local expected="$2"
|
|
57
|
+
local body="${3:-}"
|
|
58
|
+
local label="$4"
|
|
59
|
+
|
|
60
|
+
local actual
|
|
61
|
+
actual=$(_analyzer_for_commit "$subject" "$body")
|
|
62
|
+
assert_equals "$expected" "$actual" "$label"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
echo "Testing conventional commit bump detection..."
|
|
66
|
+
|
|
67
|
+
_run_case "feat: add login" "minor" "" "feat: → minor"
|
|
68
|
+
_run_case "feat(auth): add login" "minor" "" "feat(scope): → minor"
|
|
69
|
+
_run_case "fix: handle null" "patch" "" "fix: → patch"
|
|
70
|
+
_run_case "fix(api): null guard" "patch" "" "fix(scope): → patch"
|
|
71
|
+
_run_case "chore: cleanup" "patch" "" "chore: → patch"
|
|
72
|
+
_run_case "docs(readme): typo" "patch" "" "docs(scope): → patch"
|
|
73
|
+
_run_case "ci(release): tweak" "patch" "" "ci(scope): → patch"
|
|
74
|
+
|
|
75
|
+
echo -e "\nTesting breaking-change detection..."
|
|
76
|
+
|
|
77
|
+
_run_case "feat!: drop legacy api" "major" "" "feat!: → major"
|
|
78
|
+
_run_case "feat(installer)!: modular rewrite" "major" "" "feat(scope)!: → major"
|
|
79
|
+
_run_case "refactor(api)!: rename endpoint" "major" "" "refactor(scope)!: → major"
|
|
80
|
+
_run_case "fix!: change return shape" "major" "" "fix!: → major"
|
|
81
|
+
_run_case "feat: redesign" "major" "BREAKING CHANGE: removes old config" "BREAKING CHANGE footer → major"
|
|
82
|
+
_run_case "feat: redesign" "major" "BREAKING-CHANGE: removes old config" "BREAKING-CHANGE footer → major"
|
|
83
|
+
|
|
84
|
+
echo -e "\nTesting stdout/stderr separation (regression for silent log_info bug)..."
|
|
85
|
+
|
|
86
|
+
# The whole point: with stderr discarded, stdout MUST be exactly one of
|
|
87
|
+
# patch|minor|major|none. If log helpers leak to stdout, this fails.
|
|
88
|
+
out=$(_analyzer_for_commit "feat: anything")
|
|
89
|
+
case "$out" in
|
|
90
|
+
patch|minor|major|none) assert_true "true" "stdout contains only the bump type" ;;
|
|
91
|
+
*) assert_true "false" "stdout contains only the bump type (got: '$out')" ;;
|
|
92
|
+
esac
|
|
93
|
+
|
|
94
|
+
# And with stderr captured we should also see log lines — proving they exist
|
|
95
|
+
# but go to the right stream.
|
|
96
|
+
tmp=$(mktemp -d)
|
|
97
|
+
(
|
|
98
|
+
cd "$tmp" || exit 1
|
|
99
|
+
git init -q -b main 2>/dev/null || git init -q
|
|
100
|
+
git config user.email "test@example.com"
|
|
101
|
+
git config user.name "Test"
|
|
102
|
+
git config commit.gpgsign false
|
|
103
|
+
echo seed > a; git add a; git commit -q -m "chore: seed"
|
|
104
|
+
echo x > b; git add b; git commit -q -m "feat: x"
|
|
105
|
+
"$ANALYZER" HEAD~1..HEAD 2>err.log >out.log
|
|
106
|
+
grep -qE "Analyzing|bump" err.log
|
|
107
|
+
) && assert_true "true" "Logs are written to stderr" \
|
|
108
|
+
|| assert_true "false" "Logs are written to stderr"
|
|
109
|
+
rm -rf "$tmp"
|
|
@@ -18,10 +18,17 @@ source "$LIB_DIR/common.sh"
|
|
|
18
18
|
COMMIT_RANGE="${1:-HEAD~1..HEAD}"
|
|
19
19
|
DEBUG=${DEBUG:-false}
|
|
20
20
|
|
|
21
|
-
#
|
|
21
|
+
# ----------------------------------------------------------------------------
|
|
22
|
+
# IMPORTANT: stdout is reserved for the bump-type result (patch|minor|major|none).
|
|
23
|
+
# All log output MUST go to stderr or it will be captured by callers like the
|
|
24
|
+
# version-bump workflow that uses `BUMP_TYPE=$(./analyze-commits ...)`.
|
|
25
|
+
# ----------------------------------------------------------------------------
|
|
26
|
+
log_info() { echo -e "${BLUE}[INFO]${NC} $*" >&2; }
|
|
27
|
+
log_warning() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; }
|
|
28
|
+
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
|
22
29
|
log_debug() {
|
|
23
|
-
if [[ "$DEBUG" == "true" ]]; then
|
|
24
|
-
|
|
30
|
+
if [[ "$DEBUG" == "true" ]] || [[ "${VERBOSE:-false}" == "true" ]]; then
|
|
31
|
+
echo -e "${PURPLE}[DEBUG]${NC} $*" >&2
|
|
25
32
|
fi
|
|
26
33
|
}
|
|
27
34
|
|
|
@@ -38,14 +45,14 @@ analyze_commit() {
|
|
|
38
45
|
log_debug "Analyzing commit: $commit_hash"
|
|
39
46
|
log_debug "Message: $(echo "$commit_message" | head -1)"
|
|
40
47
|
|
|
41
|
-
# Check for breaking changes (MAJOR)
|
|
42
|
-
if echo "$commit_message" | grep -
|
|
48
|
+
# Check for breaking changes in body (MAJOR) — Conventional Commits spec
|
|
49
|
+
if echo "$commit_message" | grep -qiE "^BREAKING[ -]CHANGE:|^BREAKING:"; then
|
|
43
50
|
echo "major"
|
|
44
51
|
return 0
|
|
45
52
|
fi
|
|
46
53
|
|
|
47
54
|
# Check for major version indicators in commit message
|
|
48
|
-
if echo "$commit_message" | grep -qE "^(major|MAJOR|breaking|BREAKING)[
|
|
55
|
+
if echo "$commit_message" | grep -qE "^(major|MAJOR|breaking|BREAKING)[[:space:]:]"; then
|
|
49
56
|
echo "major"
|
|
50
57
|
return 0
|
|
51
58
|
fi
|
|
@@ -53,32 +60,39 @@ analyze_commit() {
|
|
|
53
60
|
# Check commit message patterns for conventional commits
|
|
54
61
|
local subject_line=$(echo "$commit_message" | head -1)
|
|
55
62
|
|
|
63
|
+
# Conventional Commits "!" breaking-change marker (MAJOR)
|
|
64
|
+
# Matches: feat!:, fix!:, feat(scope)!:, refactor(api)!:, etc.
|
|
65
|
+
if echo "$subject_line" | grep -qE "^[a-zA-Z]+(\([^)]+\))?!:"; then
|
|
66
|
+
echo "major"
|
|
67
|
+
return 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
56
70
|
# MAJOR changes
|
|
57
|
-
if echo "$subject_line" | grep -qE "^(revert|remove|delete)[
|
|
71
|
+
if echo "$subject_line" | grep -qE "^(revert|remove|delete)(\([^)]+\))?:.*[Bb]reaking"; then
|
|
58
72
|
echo "major"
|
|
59
73
|
return 0
|
|
60
74
|
fi
|
|
61
75
|
|
|
62
|
-
# MINOR changes (new features)
|
|
63
|
-
if echo "$subject_line" | grep -qE "^(feat|feature|add|new)[
|
|
76
|
+
# MINOR changes (new features) — supports optional scope, e.g. feat(api):
|
|
77
|
+
if echo "$subject_line" | grep -qE "^(feat|feature|add|new)(\([^)]+\))?:"; then
|
|
64
78
|
echo "minor"
|
|
65
79
|
return 0
|
|
66
80
|
fi
|
|
67
81
|
|
|
68
82
|
# MINOR changes - significant additions
|
|
69
|
-
if echo "$subject_line" | grep -
|
|
83
|
+
if echo "$subject_line" | grep -qiE "^(enhance|improve|update)(\([^)]+\))?:.*feature"; then
|
|
70
84
|
echo "minor"
|
|
71
85
|
return 0
|
|
72
86
|
fi
|
|
73
87
|
|
|
74
|
-
# PATCH changes (bug fixes, small improvements)
|
|
75
|
-
if echo "$subject_line" | grep -qE "^(fix|bug|patch|hotfix|chore|docs|style|refactor|test|perf)[
|
|
88
|
+
# PATCH changes (bug fixes, small improvements) — supports optional scope
|
|
89
|
+
if echo "$subject_line" | grep -qE "^(fix|bug|patch|hotfix|chore|docs|style|refactor|test|perf|ci|build)(\([^)]+\))?:"; then
|
|
76
90
|
echo "patch"
|
|
77
91
|
return 0
|
|
78
92
|
fi
|
|
79
93
|
|
|
80
94
|
# PATCH changes - maintenance and small improvements
|
|
81
|
-
if echo "$subject_line" | grep -qE "^(update|improve|enhance|optimize|clean)[
|
|
95
|
+
if echo "$subject_line" | grep -qE "^(update|improve|enhance|optimize|clean)(\([^)]+\))?:"; then
|
|
82
96
|
echo "patch"
|
|
83
97
|
return 0
|
|
84
98
|
fi
|
|
@@ -288,10 +302,16 @@ ENVIRONMENT:
|
|
|
288
302
|
DEBUG=true # Enable debug output
|
|
289
303
|
|
|
290
304
|
CONVENTIONAL COMMIT PATTERNS:
|
|
291
|
-
feat:, feature:, add:
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
305
|
+
feat:, feature:, add: → minor
|
|
306
|
+
feat(scope): → minor
|
|
307
|
+
fix:, bug:, patch: → patch
|
|
308
|
+
feat!:, fix!:, refactor(api)!: → major (Conventional Commits "!" marker)
|
|
309
|
+
BREAKING CHANGE:, BREAKING: → major (commit body footer)
|
|
310
|
+
chore:, docs:, style:, ci: → patch
|
|
311
|
+
|
|
312
|
+
OUTPUT CONTRACT:
|
|
313
|
+
Only the bump type is written to stdout. All progress/log output goes to
|
|
314
|
+
stderr so callers can safely use \`BUMP=\$(./analyze-commits ...)\`.
|
|
295
315
|
EOF
|
|
296
316
|
exit 0
|
|
297
317
|
fi
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-theme-zer0
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amr Abdel
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|
|
@@ -355,6 +355,7 @@ files:
|
|
|
355
355
|
- scripts/README.md
|
|
356
356
|
- scripts/analyze-commits.sh
|
|
357
357
|
- scripts/bin/build
|
|
358
|
+
- scripts/bin/install
|
|
358
359
|
- scripts/bin/release
|
|
359
360
|
- scripts/bin/test
|
|
360
361
|
- scripts/build
|
|
@@ -378,6 +379,27 @@ files:
|
|
|
378
379
|
- scripts/lib/frontmatter.sh
|
|
379
380
|
- scripts/lib/gem.sh
|
|
380
381
|
- scripts/lib/git.sh
|
|
382
|
+
- scripts/lib/install/README.md
|
|
383
|
+
- scripts/lib/install/agents.sh
|
|
384
|
+
- scripts/lib/install/ai/diagnose.sh
|
|
385
|
+
- scripts/lib/install/ai/openai.sh
|
|
386
|
+
- scripts/lib/install/ai/suggest.sh
|
|
387
|
+
- scripts/lib/install/ai/wizard.sh
|
|
388
|
+
- scripts/lib/install/config.sh
|
|
389
|
+
- scripts/lib/install/deploy/README.md
|
|
390
|
+
- scripts/lib/install/deploy/azure-swa.sh
|
|
391
|
+
- scripts/lib/install/deploy/docker-prod.sh
|
|
392
|
+
- scripts/lib/install/deploy/github-pages.sh
|
|
393
|
+
- scripts/lib/install/deploy/registry.sh
|
|
394
|
+
- scripts/lib/install/doctor.sh
|
|
395
|
+
- scripts/lib/install/fs.sh
|
|
396
|
+
- scripts/lib/install/logging.sh
|
|
397
|
+
- scripts/lib/install/pages.sh
|
|
398
|
+
- scripts/lib/install/platform.sh
|
|
399
|
+
- scripts/lib/install/profile.sh
|
|
400
|
+
- scripts/lib/install/template.sh
|
|
401
|
+
- scripts/lib/install/upgrade.sh
|
|
402
|
+
- scripts/lib/install/wizard_interactive.sh
|
|
381
403
|
- scripts/lib/migrate.sh
|
|
382
404
|
- scripts/lib/preview_generator.py
|
|
383
405
|
- scripts/lib/template.sh
|
|
@@ -399,6 +421,7 @@ files:
|
|
|
399
421
|
- scripts/test/integration/auto-version
|
|
400
422
|
- scripts/test/integration/mermaid
|
|
401
423
|
- scripts/test/lib/run_tests.sh
|
|
424
|
+
- scripts/test/lib/test_analyze_commits.sh
|
|
402
425
|
- scripts/test/lib/test_changelog.sh
|
|
403
426
|
- scripts/test/lib/test_gem.sh
|
|
404
427
|
- scripts/test/lib/test_git.sh
|