-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbuild.sh
More file actions
executable file
·487 lines (422 loc) · 12 KB
/
build.sh
File metadata and controls
executable file
·487 lines (422 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
#!/bin/bash
#
# build.sh - Build and validate dotfiles locally
#
# Usage:
# ./build.sh # Run all checks
# ./build.sh lint # Only lint
# ./build.sh test # Only run tests
# ./build.sh cli # Test dotfiles CLI
# ./build.sh brewfile # Validate Brewfile
# ./build.sh markdown # Lint markdown files
# ./build.sh validate # Only validate templates
# ./build.sh --help # Show help
#
set -e
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TOOLS_DIR="$ROOT_DIR/tools"
# Colors
if [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'
else
RED='' GREEN='' YELLOW='' BLUE='' BOLD='' NC=''
fi
# Counters
PASS=0
FAIL=0
WARN=0
say() { echo -e "${GREEN}[build]${NC} $1"; }
info() { echo -e "${BLUE}→${NC} $1"; }
warn() { echo -e "${YELLOW}!${NC} $1"; WARN=$((WARN + 1)); }
fail() { echo -e "${RED}✗${NC} $1"; FAIL=$((FAIL + 1)); }
pass() { echo -e "${GREEN}✓${NC} $1"; PASS=$((PASS + 1)); }
header() { echo -e "\n${BOLD}$1${NC}"; }
show_help() {
echo -e "${BOLD}build.sh${NC} - Build and validate dotfiles"
echo ""
echo -e "${BOLD}Usage:${NC}"
echo " ./build.sh [command]"
echo ""
echo -e "${BOLD}Commands:${NC}"
echo " all Run all checks (default)"
echo " lint Lint shell scripts with ShellCheck"
echo " syntax Check bash syntax"
echo " validate Validate chezmoi templates"
echo " test Run doctor in test mode"
echo " cli Test dotfiles CLI commands"
echo " brewfile Validate Brewfile"
echo " markdown Lint markdown files"
echo " count Count files and stats"
echo " help Show this help"
echo ""
}
# Check if a command exists
has_cmd() {
command -v "$1" &>/dev/null
}
# Get all shell scripts in the repo
get_shell_scripts() {
# Known directories containing shell scripts
local dirs=(
"$ROOT_DIR/tools"
"$ROOT_DIR/tools/os_installers"
"$ROOT_DIR/tools/os_setup"
"$ROOT_DIR/scripts"
"$ROOT_DIR/scripts/backup"
"$ROOT_DIR/cron"
"$ROOT_DIR"
)
for dir in "${dirs[@]}"; do
[[ -d "$dir" ]] || continue
for f in "$dir"/*.sh; do
[[ -f "$f" ]] && echo "$f"
done
done
}
# Get all template files
get_template_files() {
# Recursively find .tmpl files in home/
for dir in "$ROOT_DIR"/home "$ROOT_DIR"/home/*/ "$ROOT_DIR"/home/*/*/ "$ROOT_DIR"/home/*/*/*/; do
[[ -d "$dir" ]] || continue
for f in "$dir"/*.tmpl; do
[[ -f "$f" ]] && echo "$f"
done
done 2>/dev/null
}
# Get all markdown files
get_markdown_files() {
for dir in "$ROOT_DIR" "$ROOT_DIR"/docs "$ROOT_DIR"/docs/commands; do
[[ -d "$dir" ]] || continue
for f in "$dir"/*.md; do
[[ -f "$f" ]] && echo "$f"
done
done
}
# Lint shell scripts
do_lint() {
header "Linting Shell Scripts"
if ! has_cmd shellcheck; then
warn "shellcheck not installed (brew install shellcheck)"
return 0
fi
local failed=0
local scripts
scripts=$(get_shell_scripts)
local OLD_IFS="$IFS"
IFS=$'\n'
for script in $scripts; do
[[ -z "$script" ]] && continue
local rel_path="${script#$ROOT_DIR/}"
if shellcheck --severity=warning "$script" 2>/dev/null; then
pass "$rel_path"
else
fail "$rel_path"
failed=$((failed + 1))
fi
done
IFS="$OLD_IFS"
# Add dotfiles CLI
if [[ -f "$ROOT_DIR/tools/dotfiles" ]]; then
if shellcheck --severity=warning "$ROOT_DIR/tools/dotfiles" 2>/dev/null; then
pass "tools/dotfiles"
else
fail "tools/dotfiles"
failed=$((failed + 1))
fi
fi
if [[ $failed -eq 0 ]]; then
say "All scripts passed linting"
else
say "$failed script(s) have warnings"
fi
}
# Check bash syntax
do_syntax() {
header "Checking Bash Syntax"
local failed=0
local scripts
scripts=$(get_shell_scripts)
local OLD_IFS="$IFS"
IFS=$'\n'
for script in $scripts; do
[[ -z "$script" ]] && continue
local rel_path="${script#$ROOT_DIR/}"
if bash -n "$script" 2>/dev/null; then
pass "$rel_path"
else
fail "$rel_path"
failed=$((failed + 1))
fi
done
IFS="$OLD_IFS"
# Add dotfiles CLI
if [[ -f "$ROOT_DIR/tools/dotfiles" ]]; then
if bash -n "$ROOT_DIR/tools/dotfiles" 2>/dev/null; then
pass "tools/dotfiles"
else
fail "tools/dotfiles"
failed=$((failed + 1))
fi
fi
if [[ $failed -eq 0 ]]; then
say "All scripts have valid syntax"
else
fail "$failed script(s) have syntax errors"
return 1
fi
}
# Validate chezmoi templates
do_validate() {
header "Validating Chezmoi Templates"
if ! has_cmd chezmoi; then
warn "chezmoi not installed"
return 0
fi
# Get template files
local template_count=0
local failed=0
local templates
templates=$(get_template_files)
local OLD_IFS="$IFS"
IFS=$'\n'
for tmpl in $templates; do
[[ -z "$tmpl" ]] && continue
template_count=$((template_count + 1))
local rel_path="${tmpl#$ROOT_DIR/}"
# Basic check: look for unclosed {{ or }}
if grep -qE '\{\{[^}]*$' "$tmpl" 2>/dev/null; then
fail "$rel_path (unclosed template tag)"
failed=$((failed + 1))
elif grep -qE '^[^{]*\}\}' "$tmpl" 2>/dev/null; then
fail "$rel_path (orphan closing tag)"
failed=$((failed + 1))
else
pass "$rel_path"
fi
done
IFS="$OLD_IFS"
info "Found $template_count template files"
if [[ $failed -eq 0 ]]; then
say "All templates look valid"
else
fail "$failed template(s) have issues"
fi
}
# Run doctor as a test
do_test() {
header "Running Doctor (Test Mode)"
if [[ -f "$TOOLS_DIR/doctor.sh" ]]; then
if bash "$TOOLS_DIR/doctor.sh" --quick; then
pass "Doctor passed"
else
warn "Doctor reported issues (may be expected)"
fi
else
fail "doctor.sh not found"
fi
}
# Test dotfiles CLI
do_test_cli() {
header "Testing Dotfiles CLI"
local cli="$TOOLS_DIR/dotfiles"
if [[ ! -f "$cli" ]]; then
fail "dotfiles CLI not found"
return 1
fi
# Test help command
if bash "$cli" help &>/dev/null; then
pass "dotfiles help"
else
fail "dotfiles help"
fi
# Test cd command
local cd_output
cd_output=$(bash "$cli" cd 2>/dev/null)
if [[ -n "$cd_output" ]]; then
pass "dotfiles cd → $cd_output"
else
fail "dotfiles cd"
fi
# Test status command (if in git repo)
if [[ -d "$ROOT_DIR/.git" ]]; then
if bash "$cli" status &>/dev/null; then
pass "dotfiles status"
else
warn "dotfiles status (may need git setup)"
fi
fi
}
# Validate Brewfile
do_brewfile() {
header "Validating Brewfile"
local brewfile="$ROOT_DIR/Brewfile"
if [[ ! -f "$brewfile" ]]; then
warn "Brewfile not found"
return 0
fi
# Count entries
local formulae casks taps
formulae=$(grep -cE "^brew " "$brewfile" 2>/dev/null || echo 0)
casks=$(grep -cE "^cask " "$brewfile" 2>/dev/null || echo 0)
taps=$(grep -cE "^tap " "$brewfile" 2>/dev/null || echo 0)
info "Formulae: $formulae, Casks: $casks, Taps: $taps"
# Check for syntax issues (basic validation)
local issues=0
# Check for duplicate entries
local dupes
dupes=$(grep -E "^(brew|cask) " "$brewfile" | sort | uniq -d)
if [[ -n "$dupes" ]]; then
fail "Duplicate Brewfile entries found:"
echo "$dupes" | while read -r line; do
echo " $line"
done
issues=$((issues + 1))
fi
# Check for empty quotes
if grep -qE '(brew|cask) ""' "$brewfile"; then
fail "Empty package name in Brewfile"
issues=$((issues + 1))
fi
# Validate with brew bundle if available
if has_cmd brew; then
if brew bundle check --file="$brewfile" &>/dev/null; then
pass "Brewfile syntax valid (brew bundle check)"
else
warn "Some Brewfile packages not installed (expected)"
fi
else
info "Skipping brew bundle check (Homebrew not installed)"
fi
if [[ $issues -eq 0 ]]; then
pass "Brewfile validation passed"
fi
}
# Lint markdown files
do_markdown() {
header "Linting Markdown"
local md_count=0
local md_files
md_files=$(get_markdown_files)
# Check if markdownlint is available
if has_cmd markdownlint; then
local OLD_IFS="$IFS"
IFS=$'\n'
for md_file in $md_files; do
[[ -z "$md_file" ]] && continue
md_count=$((md_count + 1))
local rel_path="${md_file#$ROOT_DIR/}"
if markdownlint "$md_file" &>/dev/null; then
pass "$rel_path"
else
warn "$rel_path (has lint warnings)"
fi
done
IFS="$OLD_IFS"
elif has_cmd npx && [[ -f "$ROOT_DIR/node_modules/.bin/markdownlint" ]]; then
info "Using npx markdownlint..."
npx markdownlint "**/*.md" --ignore node_modules 2>/dev/null || warn "Markdown lint warnings found"
else
# Count markdown files
local OLD_IFS="$IFS"
IFS=$'\n'
for _ in $md_files; do
md_count=$((md_count + 1))
done
IFS="$OLD_IFS"
info "markdownlint not installed (npm install -g markdownlint-cli)"
info "Found $md_count markdown files (install markdownlint for detailed checks)"
fi
}
# Count files and stats
do_count() {
header "Repository Statistics"
cd "$ROOT_DIR"
# Count files using helper functions
local shell_count=0
local template_count=0
local doc_count=0
local total_lines=0
local scripts templates docs
scripts=$(get_shell_scripts)
templates=$(get_template_files)
docs=$(get_markdown_files)
local OLD_IFS="$IFS"
IFS=$'\n'
for f in $scripts; do
[[ -z "$f" ]] && continue
shell_count=$((shell_count + 1))
[[ -f "$f" ]] && total_lines=$((total_lines + $(wc -l < "$f")))
done
for _ in $templates; do
template_count=$((template_count + 1))
done
for _ in $docs; do
doc_count=$((doc_count + 1))
done
IFS="$OLD_IFS"
# Count Brewfile entries
local brew_formulae brew_casks
brew_formulae=$(grep -cE "^brew " Brewfile 2>/dev/null || echo 0)
brew_casks=$(grep -cE "^cask " Brewfile 2>/dev/null || echo 0)
echo ""
echo " Shell scripts: $shell_count"
echo " Templates: $template_count"
echo " Documentation: $doc_count"
echo ""
echo " Brewfile formulae: $brew_formulae"
echo " Brewfile casks: $brew_casks"
echo ""
echo " Total shell lines: $total_lines"
}
# Run all checks
do_all() {
say "Running all build checks..."
echo ""
do_syntax
do_lint
do_validate
do_test
do_test_cli
do_brewfile
do_markdown
do_count
# Summary
header "Build Summary"
echo -e "${GREEN}✓ $PASS passed${NC}"
if [[ $WARN -gt 0 ]]; then
echo -e "${YELLOW}! $WARN warnings${NC}"
fi
if [[ $FAIL -gt 0 ]]; then
echo -e "${RED}✗ $FAIL failed${NC}"
exit 1
fi
echo ""
say "Build completed successfully!"
}
# Main
main() {
cd "$ROOT_DIR"
case "${1:-all}" in
all) do_all ;;
lint) do_lint ;;
syntax) do_syntax ;;
validate) do_validate ;;
test) do_test ;;
cli) do_test_cli ;;
brewfile) do_brewfile ;;
markdown) do_markdown ;;
count) do_count ;;
help|--help|-h) show_help ;;
*)
fail "Unknown command: $1"
show_help
exit 1
;;
esac
}
main "$@"