@digitoimistodude/code-quality-checks 2.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.
- package/.husky/commit-msg +132 -0
- package/.husky/pre-commit +756 -0
- package/.husky/pre-commit-config +13 -0
- package/README.md +109 -0
- package/index.js +20 -0
- package/package.json +41 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Commit message validation hook for Dude projects
|
|
3
|
+
# Ref: DEV-177, DEV-373, DEV-623
|
|
4
|
+
|
|
5
|
+
# Colors for output
|
|
6
|
+
RED='\033[1;31m'
|
|
7
|
+
GREEN='\033[0;32m'
|
|
8
|
+
YELLOW='\033[1;33m'
|
|
9
|
+
BLUE='\033[1;34m'
|
|
10
|
+
PURPLE='\033[0;35m'
|
|
11
|
+
NC='\033[0m' # No Color
|
|
12
|
+
|
|
13
|
+
# Emojis for better visual feedback
|
|
14
|
+
CHECK="✅"
|
|
15
|
+
CROSS="❌"
|
|
16
|
+
ARROW="➜"
|
|
17
|
+
SEARCH="🔍"
|
|
18
|
+
|
|
19
|
+
echo ""
|
|
20
|
+
echo "${SEARCH} Checking commit message format..."
|
|
21
|
+
|
|
22
|
+
# Get the commit message from the file passed as argument
|
|
23
|
+
commit_msg_file="$1"
|
|
24
|
+
if [ -f "$commit_msg_file" ]; then
|
|
25
|
+
commit_msg=$(cat "$commit_msg_file")
|
|
26
|
+
printf " ${ARROW} Validating commit message... "
|
|
27
|
+
|
|
28
|
+
# Check for task ID pattern (e.g., DEV-123, TASK-456, etc.)
|
|
29
|
+
task_id_pattern="[A-Z]+-[0-9]+"
|
|
30
|
+
|
|
31
|
+
# Check for magic words (closing)
|
|
32
|
+
closing_words="close|closes|closed|closing|fix|fixes|fixed|fixing|resolve|resolves|resolved|resolving|complete|completes|completed|completing"
|
|
33
|
+
|
|
34
|
+
# Check for magic words (non-closing)
|
|
35
|
+
non_closing_words="ref|refs|references|part of|related to|contributes to|toward|towards"
|
|
36
|
+
|
|
37
|
+
# Check if commit message contains task ID
|
|
38
|
+
if ! echo "$commit_msg" | grep -iE "$task_id_pattern" >/dev/null 2>&1; then
|
|
39
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
40
|
+
echo " ${RED}Commit message missing task ID${NC}"
|
|
41
|
+
echo " ${YELLOW}Your commit message:${NC}"
|
|
42
|
+
echo " ${BLUE}\"$commit_msg\"${BLUE}"
|
|
43
|
+
echo ""
|
|
44
|
+
echo " ${YELLOW}Expected format: [MAGIC_WORD]: [TASK-ID]${NC}"
|
|
45
|
+
echo " ${YELLOW}Correct example: ${BLUE}\"$commit_msg, Ref: DEV-123\"${NC}"
|
|
46
|
+
echo " ${YELLOW}See: ${NC}${BLUE}https://linear.app/docs/github?tabs=206cad22125a#link-through-pull-requests${NC}"
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Check if commit message contains magic words
|
|
51
|
+
if ! echo "$commit_msg" | grep -iE "($closing_words|$non_closing_words)" >/dev/null 2>&1; then
|
|
52
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
53
|
+
echo " ${RED}Commit message missing magic words${NC}"
|
|
54
|
+
echo " ${YELLOW}Your commit message:${NC}"
|
|
55
|
+
echo " ${PURPLE}\"$commit_msg\"${NC}"
|
|
56
|
+
echo " ${YELLOW}Closing magic words: ${NC}${PURPLE}close, closes, closed, closing, fix, fixes, fixed, fixing, resolve, resolves, resolved, resolving, complete, completes, completed, completing${NC}"
|
|
57
|
+
echo " ${YELLOW}Non-closing magic words: ${NC}${PURPLE}ref, refs, references, part of, related to, contributes to, toward, towards${NC}"
|
|
58
|
+
# Extract task ID from commit message if present for dynamic example
|
|
59
|
+
extracted_task_id=$(echo "$commit_msg" | grep -ioE "$task_id_pattern" | head -1 || echo "DEV-123")
|
|
60
|
+
echo " ${YELLOW}Correct examples:${NC}"
|
|
61
|
+
echo " ${PURPLE}\"$commit_msg, Closes $extracted_task_id\"${NC}"
|
|
62
|
+
echo " ${PURPLE}\"$commit_msg, Ref: $extracted_task_id\"${NC}"
|
|
63
|
+
echo " ${YELLOW}See: ${NC}${PURPLE}https://linear.app/docs/github?tabs=206cad22125a#link-through-pull-requests${NC}"
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
68
|
+
|
|
69
|
+
# If we reach this point, ALL hooks have passed - show success box
|
|
70
|
+
echo ""
|
|
71
|
+
|
|
72
|
+
# Create ASCII box with reminders
|
|
73
|
+
print_success_box() {
|
|
74
|
+
local box_width=60
|
|
75
|
+
local border="${GREEN}╔$(printf '═%.0s' $(seq 1 $((box_width-2))))╗${NC}"
|
|
76
|
+
local bottom="${GREEN}╚$(printf '═%.0s' $(seq 1 $((box_width-2))))╝${NC}"
|
|
77
|
+
|
|
78
|
+
echo "$border"
|
|
79
|
+
echo "${GREEN}║${NC} ${GREEN}✅ All checks passed! Ready to push!${NC}$(printf '%*s' 21 '')${GREEN}║${NC}"
|
|
80
|
+
echo "${GREEN}║${NC}$(printf '%*s' 58 '')${GREEN}║${NC}"
|
|
81
|
+
echo "${GREEN}║${NC} ${GREEN}💡 Reminders before pushing:${NC}$(printf '%*s' 29 '')${GREEN}║${NC}"
|
|
82
|
+
echo "${GREEN}║${NC}$(printf '%*s' 58 '')${GREEN}║${NC}"
|
|
83
|
+
|
|
84
|
+
# Check CHANGELOG.md age
|
|
85
|
+
if [ -f "CHANGELOG.md" ]; then
|
|
86
|
+
changelog_age_minutes=$(find CHANGELOG.md -mmin +0 -printf '%Tm\n' 2>/dev/null || echo "0")
|
|
87
|
+
if [ "$changelog_age_minutes" -gt 10 ]; then
|
|
88
|
+
echo "${GREEN}║${NC} ${GREEN}• Update CHANGELOG.md with recent changes${NC}$(printf '%*s' 20 '')${GREEN}║${NC}"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# Check if CHANGELOG.md has today's date
|
|
92
|
+
changelog_first_line=$(head -n 1 CHANGELOG.md)
|
|
93
|
+
current_date=$(date +%Y-%m-%d)
|
|
94
|
+
if ! echo "$changelog_first_line" | grep -q "$current_date"; then
|
|
95
|
+
echo "${GREEN}║${NC} ${GREEN}• Consider updating changelog date to $current_date${NC}$(printf '%*s' 10 '')${GREEN}║${NC}"
|
|
96
|
+
fi
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Check for uncommitted files
|
|
100
|
+
uncommitted_files=$(git status --porcelain | wc -l | tr -d ' ')
|
|
101
|
+
if [ "$uncommitted_files" -gt 0 ]; then
|
|
102
|
+
if [ "$uncommitted_files" -ge 10 ]; then
|
|
103
|
+
echo "${GREEN}║${NC} ${GREEN}• You have $uncommitted_files uncommitted files${NC}$(printf '%*s' 27 '')${GREEN}║${NC}"
|
|
104
|
+
else
|
|
105
|
+
echo "${GREEN}║${NC} ${GREEN}• You have $uncommitted_files uncommitted files${NC}$(printf '%*s' 27 '')${GREEN}║${NC}"
|
|
106
|
+
fi
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# Check current branch
|
|
110
|
+
current_branch=$(git branch --show-current 2>/dev/null || echo "unknown")
|
|
111
|
+
if [ "$current_branch" = "master" ] || [ "$current_branch" = "main" ]; then
|
|
112
|
+
echo "${GREEN}║${NC} ${GREEN}• You're on '$current_branch' - use task ID branches, if possible ${NC}$(printf '%*s' 0 '')${GREEN}║${NC}"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# General reminders
|
|
116
|
+
echo "${GREEN}║${NC} ${GREEN}• Test changes locally before pushing${NC}$(printf '%*s' 20 '')${GREEN}║${NC}"
|
|
117
|
+
echo "${GREEN}║${NC} ${GREEN}• When in doubt, huddle calls and docs are your friends${NC}$(printf '%*s' 2 '')${GREEN}║${NC}"
|
|
118
|
+
|
|
119
|
+
# Check if this is Friday afternoon
|
|
120
|
+
day_of_week=$(date +%u) # 1=Monday, 7=Sunday
|
|
121
|
+
hour_of_day=$(date +%H)
|
|
122
|
+
if [ "$day_of_week" -eq 5 ] && [ "$hour_of_day" -ge 15 ]; then
|
|
123
|
+
echo "${GREEN}║${NC} ${GREEN}⚠️ Friday afternoon - defer non-critical deploys${NC}$(printf '%*s' 8 '')${GREEN}║${NC}"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
echo "$bottom"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
print_success_box
|
|
130
|
+
else
|
|
131
|
+
echo "${YELLOW}⚠️ SKIPPED (no commit message file)${NC}"
|
|
132
|
+
fi
|
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
# Pre commit hooks for Dude projects
|
|
2
|
+
# Ref: DEV-177, DEV-373, DEV-623
|
|
3
|
+
|
|
4
|
+
# Version:
|
|
5
|
+
VERSION="2.0.0 (2025-12-01)"
|
|
6
|
+
|
|
7
|
+
# Load configuration
|
|
8
|
+
# First check for local project config, then fall back to package default
|
|
9
|
+
SCRIPT_DIR="$(dirname "$0")"
|
|
10
|
+
if [ -f ".husky/pre-commit-config" ]; then
|
|
11
|
+
. ".husky/pre-commit-config"
|
|
12
|
+
elif [ -f "$SCRIPT_DIR/pre-commit-config" ]; then
|
|
13
|
+
. "$SCRIPT_DIR/pre-commit-config"
|
|
14
|
+
else
|
|
15
|
+
# Default configuration
|
|
16
|
+
EXCLUDE_DIRS=".husky|node_modules|vendor|.git|uploads|cache"
|
|
17
|
+
EXCLUDE_FILES="*.min.js|*.min.css|*.map|*.lock|*.log|composer.lock|package-lock.json|yarn.lock"
|
|
18
|
+
EXCLUDE_EXTENSIONS=".jpg|.jpeg|.png|.gif|.svg|.ico|.pdf|.zip|.tar|.gz|.woff|.woff2|.ttf|.eot"
|
|
19
|
+
CHECK_CHANGED_ONLY=true
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Detect context: standalone theme or Bedrock project
|
|
23
|
+
# Bedrock projects have content/themes/ directory structure
|
|
24
|
+
detect_context() {
|
|
25
|
+
if [ -d "content/themes" ] && [ -f "composer.json" ]; then
|
|
26
|
+
echo "bedrock"
|
|
27
|
+
else
|
|
28
|
+
echo "standalone"
|
|
29
|
+
fi
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
CONTEXT=$(detect_context)
|
|
33
|
+
|
|
34
|
+
# Function to check if a file should be excluded
|
|
35
|
+
should_exclude_file() {
|
|
36
|
+
file="$1"
|
|
37
|
+
|
|
38
|
+
# Check directories to exclude
|
|
39
|
+
case "|$EXCLUDE_DIRS|" in
|
|
40
|
+
*"|${file%%/*}|"*) return 0 ;;
|
|
41
|
+
esac
|
|
42
|
+
|
|
43
|
+
# Check file patterns to exclude
|
|
44
|
+
IFS='|'
|
|
45
|
+
for pattern in $EXCLUDE_FILES; do
|
|
46
|
+
case "$file" in
|
|
47
|
+
$pattern) IFS=' '; return 0 ;;
|
|
48
|
+
esac
|
|
49
|
+
done
|
|
50
|
+
IFS=' '
|
|
51
|
+
|
|
52
|
+
# Check extensions to exclude
|
|
53
|
+
case "|$EXCLUDE_EXTENSIONS|" in
|
|
54
|
+
*"|${file##*.}|"*) return 0 ;;
|
|
55
|
+
esac
|
|
56
|
+
|
|
57
|
+
return 1 # don't exclude
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Function to get filtered staged files
|
|
61
|
+
get_filtered_staged_files() {
|
|
62
|
+
all_files=$(git diff --cached --name-only)
|
|
63
|
+
filtered_files=""
|
|
64
|
+
|
|
65
|
+
for file in $all_files; do
|
|
66
|
+
if ! should_exclude_file "$file"; then
|
|
67
|
+
filtered_files="$filtered_files $file"
|
|
68
|
+
fi
|
|
69
|
+
done
|
|
70
|
+
|
|
71
|
+
echo "$filtered_files"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Colors for output
|
|
75
|
+
RED='\033[1;31m'
|
|
76
|
+
GREEN='\033[0;32m'
|
|
77
|
+
YELLOW='\033[1;33m'
|
|
78
|
+
BLUE='\033[1;34m' # Bright blue for commands
|
|
79
|
+
PURPLE='\033[0;35m'
|
|
80
|
+
CYAN='\033[0;36m'
|
|
81
|
+
NC='\033[0m' # No Color
|
|
82
|
+
|
|
83
|
+
# Emojis for better visual feedback
|
|
84
|
+
CHECK="✅"
|
|
85
|
+
CROSS="❌"
|
|
86
|
+
ARROW="➜"
|
|
87
|
+
GEAR="⚙️"
|
|
88
|
+
PACKAGE="📦"
|
|
89
|
+
SEARCH="🔍"
|
|
90
|
+
CLEAN="🧹"
|
|
91
|
+
|
|
92
|
+
# ASCII art with colors
|
|
93
|
+
print_logo() {
|
|
94
|
+
RESET='\033[0m'
|
|
95
|
+
MINT='\033[38;2;0;128;128m'
|
|
96
|
+
|
|
97
|
+
printf "${MINT}"
|
|
98
|
+
printf "
|
|
99
|
+
███████████████ ██████ ██████ ███████████████ ████████████████
|
|
100
|
+
████████████████ ██████ ██████ █████████████████ ████████████████
|
|
101
|
+
██████ ████████ ██████ ██████ ███████ ████████ ████████
|
|
102
|
+
██████ ██████ ██████ ██████ ███████ ██████ ███████████████
|
|
103
|
+
██████ ███████ ████████ ███████ ███████ ████████ ████████
|
|
104
|
+
████████████████ ███████████████ █████████████████ ████████████████
|
|
105
|
+
██████████████ ████████████ ███████████████ ████████████████
|
|
106
|
+
"
|
|
107
|
+
printf "${RESET}"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Show the logo
|
|
111
|
+
print_logo
|
|
112
|
+
|
|
113
|
+
echo ""
|
|
114
|
+
echo "${MINT}Dude's husky quality control config v. ${VERSION}${NC}"
|
|
115
|
+
if [ "$CONTEXT" = "bedrock" ]; then
|
|
116
|
+
echo "${MINT}${GEAR} Running pre-commit checks (Bedrock project)...${NC}"
|
|
117
|
+
else
|
|
118
|
+
echo "${MINT}${GEAR} Running pre-commit checks (Standalone theme)...${NC}"
|
|
119
|
+
fi
|
|
120
|
+
echo ""
|
|
121
|
+
|
|
122
|
+
echo ""
|
|
123
|
+
|
|
124
|
+
# Check for required files
|
|
125
|
+
echo "${CYAN}${SEARCH} Checking required configuration files...${NC}"
|
|
126
|
+
|
|
127
|
+
printf " ${ARROW} Checking for .nvmrc file... "
|
|
128
|
+
if [ ! -f ".nvmrc" ]; then
|
|
129
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
130
|
+
echo " ${RED}.nvmrc file is missing${NC}"
|
|
131
|
+
echo " ${YELLOW}Please create .nvmrc file with your Node.js version:${NC}"
|
|
132
|
+
echo " ${BLUE}node --version > .nvmrc${NC}"
|
|
133
|
+
exit 1
|
|
134
|
+
fi
|
|
135
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
136
|
+
|
|
137
|
+
printf " ${ARROW} Checking for phpcs.xml file... "
|
|
138
|
+
if [ ! -f "phpcs.xml" ]; then
|
|
139
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
140
|
+
echo " ${RED}phpcs.xml file is missing${NC}"
|
|
141
|
+
echo " ${YELLOW}Please create phpcs.xml configuration file for PHP CodeSniffer${NC}"
|
|
142
|
+
exit 1
|
|
143
|
+
fi
|
|
144
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
145
|
+
|
|
146
|
+
# Bedrock-specific: Check that stylelintrc is NOT in root (should be in themes)
|
|
147
|
+
if [ "$CONTEXT" = "bedrock" ]; then
|
|
148
|
+
printf " ${ARROW} Checking for .stylelintrc in root (should not exist)... "
|
|
149
|
+
if [ -f ".stylelintrc" ] || [ -f ".stylelintrc.json" ] || [ -f ".stylelintrc.js" ] || [ -f ".stylelintrc.yml" ]; then
|
|
150
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
151
|
+
echo " ${RED}.stylelintrc file found in root directory${NC}"
|
|
152
|
+
echo " ${YELLOW}Stylelint config should only be in theme directories, please move:${NC}"
|
|
153
|
+
echo " ${BLUE}mv .stylelintrc* content/themes/your-theme/${NC}"
|
|
154
|
+
echo " ${YELLOW}Then update csstools/value-no-unknown-custom-properties importFrom path${NC}"
|
|
155
|
+
echo " ${YELLOW}And update .github/workflows/styles.yml path${NC}"
|
|
156
|
+
exit 1
|
|
157
|
+
fi
|
|
158
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
printf " ${ARROW} Checking for .github/workflows directory... "
|
|
162
|
+
if [ ! -d ".github/workflows" ]; then
|
|
163
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
164
|
+
echo " ${RED}.github/workflows directory is missing${NC}"
|
|
165
|
+
echo " ${YELLOW}Please create GitHub workflows directory:${NC}"
|
|
166
|
+
echo " ${BLUE}mkdir -p .github/workflows${NC}"
|
|
167
|
+
echo " ${YELLOW}And add your CI/CD workflow files${NC}"
|
|
168
|
+
exit 1
|
|
169
|
+
fi
|
|
170
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
171
|
+
|
|
172
|
+
printf " ${ARROW} Checking for gulpfile.js... "
|
|
173
|
+
if [ ! -f "gulpfile.js" ]; then
|
|
174
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
175
|
+
echo " ${RED}gulpfile.js is missing from root directory${NC}"
|
|
176
|
+
echo " ${YELLOW}Please ensure Gulp is properly set up${NC}"
|
|
177
|
+
exit 1
|
|
178
|
+
fi
|
|
179
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
180
|
+
|
|
181
|
+
printf " ${ARROW} Checking for gulp in package.json... "
|
|
182
|
+
if [ -f "package.json" ] && ! npm list gulp >/dev/null 2>&1; then
|
|
183
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
184
|
+
echo " ${RED}Gulp is not installed${NC}"
|
|
185
|
+
echo " ${YELLOW}Please install Gulp:${NC}"
|
|
186
|
+
echo " ${BLUE}nvm use${NC}"
|
|
187
|
+
echo " ${BLUE}npm install gulp --save-dev${NC}"
|
|
188
|
+
exit 1
|
|
189
|
+
fi
|
|
190
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
191
|
+
|
|
192
|
+
printf " ${ARROW} Checking for root CHANGELOG.md... "
|
|
193
|
+
if [ ! -f "CHANGELOG.md" ]; then
|
|
194
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
195
|
+
echo " ${RED}CHANGELOG.md is missing from root directory${NC}"
|
|
196
|
+
echo " ${YELLOW}Please create CHANGELOG.md in root:${NC}"
|
|
197
|
+
echo " ${BLUE}touch CHANGELOG.md${NC}"
|
|
198
|
+
exit 1
|
|
199
|
+
fi
|
|
200
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
201
|
+
|
|
202
|
+
# Bedrock-specific: Check CHANGELOG.md format and freshness
|
|
203
|
+
if [ "$CONTEXT" = "bedrock" ]; then
|
|
204
|
+
printf " ${ARROW} Checking CHANGELOG.md format and freshness... "
|
|
205
|
+
# Check CHANGELOG.md format and date
|
|
206
|
+
if [ -f "CHANGELOG.md" ]; then
|
|
207
|
+
first_line=$(head -n 1 CHANGELOG.md)
|
|
208
|
+
|
|
209
|
+
# Check if first line matches format: ### x.x.x: yyyy-mm-dd
|
|
210
|
+
if echo "$first_line" | grep -qE "^### [0-9]+\.[0-9]+\.[0-9]+: [0-9]{4}-[0-9]{2}-[0-9]{2}$"; then
|
|
211
|
+
# Extract date from first line
|
|
212
|
+
changelog_date=$(echo "$first_line" | sed 's/.*: //')
|
|
213
|
+
|
|
214
|
+
# Get current date in yyyy-mm-dd format
|
|
215
|
+
current_date=$(date +%Y-%m-%d)
|
|
216
|
+
|
|
217
|
+
# Convert dates to seconds for comparison (cross-platform)
|
|
218
|
+
if date -d "$changelog_date" +%s >/dev/null 2>&1; then
|
|
219
|
+
# Linux/GNU date
|
|
220
|
+
changelog_seconds=$(date -d "$changelog_date" +%s 2>/dev/null || echo "0")
|
|
221
|
+
current_seconds=$(date -d "$current_date" +%s)
|
|
222
|
+
else
|
|
223
|
+
# macOS/BSD date
|
|
224
|
+
changelog_seconds=$(date -j -f "%Y-%m-%d" "$changelog_date" +%s 2>/dev/null || echo "0")
|
|
225
|
+
current_seconds=$(date -j -f "%Y-%m-%d" "$current_date" +%s)
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
if [ "$changelog_seconds" -eq "0" ]; then
|
|
229
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
230
|
+
echo " ${RED}Invalid date format in CHANGELOG.md first line${NC}"
|
|
231
|
+
echo " ${YELLOW}Found: $first_line${NC}"
|
|
232
|
+
echo " ${YELLOW}Expected format: ### x.x.x: yyyy-mm-dd${NC}"
|
|
233
|
+
echo " ${BLUE}nano CHANGELOG.md${NC}"
|
|
234
|
+
exit 1
|
|
235
|
+
elif [ "$changelog_seconds" -ne "$current_seconds" ]; then
|
|
236
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
237
|
+
echo " ${RED}CHANGELOG.md date must be today's date to keep it current${NC}"
|
|
238
|
+
echo " ${YELLOW}Changelog date: $changelog_date${NC}"
|
|
239
|
+
echo " ${YELLOW}Current date: $current_date${NC}"
|
|
240
|
+
echo " ${YELLOW}Please update the changelog with today's date and recent changes:${NC}"
|
|
241
|
+
echo " ${BLUE}nano CHANGELOG.md${NC}"
|
|
242
|
+
exit 1
|
|
243
|
+
else
|
|
244
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
245
|
+
fi
|
|
246
|
+
else
|
|
247
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
248
|
+
echo " ${RED}CHANGELOG.md first line format is incorrect${NC}"
|
|
249
|
+
echo " ${YELLOW}Found: $first_line${NC}"
|
|
250
|
+
echo " ${YELLOW}Expected format: ### x.x.x: yyyy-mm-dd${NC}"
|
|
251
|
+
echo " ${YELLOW}Example: ### 1.0.0: 2025-01-15${NC}"
|
|
252
|
+
echo " ${BLUE}nano CHANGELOG.md${NC}"
|
|
253
|
+
exit 1
|
|
254
|
+
fi
|
|
255
|
+
else
|
|
256
|
+
echo "${YELLOW}⚠️ NOT FOUND${NC}"
|
|
257
|
+
fi
|
|
258
|
+
|
|
259
|
+
printf " ${ARROW} Checking for CHANGELOG.md in themes (should not exist)... "
|
|
260
|
+
changelog_in_themes=""
|
|
261
|
+
for theme_dir in content/themes/*/; do
|
|
262
|
+
if [ -f "${theme_dir}CHANGELOG.md" ]; then
|
|
263
|
+
changelog_in_themes="$changelog_in_themes ${theme_dir}CHANGELOG.md"
|
|
264
|
+
fi
|
|
265
|
+
done
|
|
266
|
+
|
|
267
|
+
if [ -n "$changelog_in_themes" ]; then
|
|
268
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
269
|
+
echo " ${RED}CHANGELOG.md found in theme directories (should only be in root):${NC}"
|
|
270
|
+
for file in $changelog_in_themes; do
|
|
271
|
+
echo " ${YELLOW}• $file${NC}"
|
|
272
|
+
done
|
|
273
|
+
echo " ${YELLOW}Please move changelog content to root CHANGELOG.md and remove:${NC}"
|
|
274
|
+
echo " ${BLUE}rm $changelog_in_themes${NC}"
|
|
275
|
+
exit 1
|
|
276
|
+
fi
|
|
277
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
278
|
+
|
|
279
|
+
# Check README.md first line for repo name
|
|
280
|
+
printf " ${ARROW} Checking README.md first line... "
|
|
281
|
+
if [ -f "README.md" ]; then
|
|
282
|
+
first_line=$(head -n 1 README.md)
|
|
283
|
+
project_name=$(basename "$(pwd)")
|
|
284
|
+
|
|
285
|
+
# Check if first line contains repo name (case sensitive)
|
|
286
|
+
if echo "$first_line" | grep -q "$project_name"; then
|
|
287
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
288
|
+
echo " ${RED}README.md first line contains repo name '$project_name'${NC}"
|
|
289
|
+
echo " ${YELLOW}Found: $first_line${NC}"
|
|
290
|
+
echo " ${YELLOW}Should contain a clear project name in sentence case${NC}"
|
|
291
|
+
echo " ${YELLOW}Example: # My Website Name${NC}"
|
|
292
|
+
echo " ${BLUE}nano README.md${NC}"
|
|
293
|
+
exit 1
|
|
294
|
+
fi
|
|
295
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
296
|
+
else
|
|
297
|
+
echo "${YELLOW}⚠️ NOT FOUND${NC}"
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
# Check theme style.css files
|
|
301
|
+
for theme_dir in content/themes/*/; do
|
|
302
|
+
if [ -f "${theme_dir}style.css" ]; then
|
|
303
|
+
theme_name=$(basename "$theme_dir")
|
|
304
|
+
printf " ${ARROW} Checking theme '${theme_name}' style.css... "
|
|
305
|
+
|
|
306
|
+
# Check Theme Name line
|
|
307
|
+
theme_name_line=$(grep "^Theme Name:" "${theme_dir}style.css" 2>/dev/null || echo "")
|
|
308
|
+
if [ -n "$theme_name_line" ]; then
|
|
309
|
+
# Check if theme name contains lowercase theme name
|
|
310
|
+
if echo "$theme_name_line" | grep -qi "Theme Name:.*$theme_name"; then
|
|
311
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
312
|
+
echo " ${RED}Theme Name contains lowercase theme name '$theme_name'${NC}"
|
|
313
|
+
echo " ${YELLOW}Found: $theme_name_line${NC}"
|
|
314
|
+
echo " ${YELLOW}Should contain a clear theme name in sentence case${NC}"
|
|
315
|
+
echo " ${YELLOW}Example: Theme Name: My Website Theme${NC}"
|
|
316
|
+
echo " ${BLUE}nano ${theme_dir}style.css${NC}"
|
|
317
|
+
exit 1
|
|
318
|
+
fi
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
# Check Description line
|
|
322
|
+
description_line=$(grep "^Description:" "${theme_dir}style.css" 2>/dev/null || echo "")
|
|
323
|
+
if [ -n "$description_line" ]; then
|
|
324
|
+
# Check if description contains default starter theme text
|
|
325
|
+
if echo "$description_line" | grep -qi "Description:.*Hi.*starter.*theme.*called"; then
|
|
326
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
327
|
+
echo " ${RED}Description contains default starter theme text${NC}"
|
|
328
|
+
echo " ${YELLOW}Found: $description_line${NC}"
|
|
329
|
+
echo " ${YELLOW}Should contain a meaningful description${NC}"
|
|
330
|
+
echo " ${YELLOW}Example: Description: Custom WordPress theme for My Website${NC}"
|
|
331
|
+
echo " ${BLUE}nano ${theme_dir}style.css${NC}"
|
|
332
|
+
exit 1
|
|
333
|
+
fi
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
# Check Version against changelog
|
|
337
|
+
version_line=$(grep "^Version:" "${theme_dir}style.css" 2>/dev/null || echo "")
|
|
338
|
+
if [ -n "$version_line" ] && [ -f "CHANGELOG.md" ]; then
|
|
339
|
+
style_version=$(echo "$version_line" | sed 's/Version: *//')
|
|
340
|
+
changelog_version=$(head -n 1 CHANGELOG.md | sed 's/### *//' | sed 's/:.*$//')
|
|
341
|
+
|
|
342
|
+
if [ "$style_version" != "$changelog_version" ]; then
|
|
343
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
344
|
+
echo " ${RED}Version mismatch between style.css and CHANGELOG.md${NC}"
|
|
345
|
+
echo " ${YELLOW}style.css version: $style_version${NC}"
|
|
346
|
+
echo " ${YELLOW}CHANGELOG.md version: $changelog_version${NC}"
|
|
347
|
+
echo " ${YELLOW}Please update version in style.css to match changelog${NC}"
|
|
348
|
+
echo " ${BLUE}nano ${theme_dir}style.css${NC}"
|
|
349
|
+
exit 1
|
|
350
|
+
fi
|
|
351
|
+
fi
|
|
352
|
+
|
|
353
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
354
|
+
fi
|
|
355
|
+
done
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
# Bedrock-specific: Check WordPress version
|
|
359
|
+
if [ "$CONTEXT" = "bedrock" ]; then
|
|
360
|
+
echo ""
|
|
361
|
+
# Check WordPress version
|
|
362
|
+
echo "${CYAN}${PACKAGE} Checking WordPress version...${NC}"
|
|
363
|
+
|
|
364
|
+
printf " ${ARROW} Checking WordPress version in composer.json... "
|
|
365
|
+
if [ -f "composer.json" ]; then
|
|
366
|
+
# Extract WordPress version from composer.json (check both variants)
|
|
367
|
+
wp_version=$(grep -o '"johnpbloch/wordpress\(-core\)\?": "[^"]*"' composer.json | sed 's/.*": "//' | sed 's/".*//')
|
|
368
|
+
|
|
369
|
+
if [ -n "$wp_version" ]; then
|
|
370
|
+
# Get latest WordPress version from API with timeout
|
|
371
|
+
latest_wp_version=$(curl -s --max-time 10 https://api.wordpress.org/core/version-check/1.7/ | grep -o '"version":"[^"]*"' | head -1 | sed 's/.*":"//' | sed 's/".*//' 2>/dev/null || echo "")
|
|
372
|
+
|
|
373
|
+
if [ -n "$latest_wp_version" ]; then
|
|
374
|
+
if [ "$wp_version" != "$latest_wp_version" ]; then
|
|
375
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
376
|
+
echo " ${RED}WordPress version in composer.json: ${wp_version}${NC}"
|
|
377
|
+
echo " ${RED}Latest WordPress version: ${latest_wp_version}${NC}"
|
|
378
|
+
echo " ${YELLOW}Please update WordPress immediately:${NC}"
|
|
379
|
+
echo " ${BLUE}sed -i '' 's/\"johnpbloch\/wordpress\": \"[^\"]*\"/\"johnpbloch\/wordpress\": \"'$latest_wp_version'\"/' composer.json${NC}"
|
|
380
|
+
echo " ${BLUE}composer update${NC}"
|
|
381
|
+
exit 1
|
|
382
|
+
else
|
|
383
|
+
echo "${GREEN}${CHECK} OK (${wp_version})${NC}"
|
|
384
|
+
fi
|
|
385
|
+
else
|
|
386
|
+
echo "${YELLOW}⚠️ UNKNOWN${NC}"
|
|
387
|
+
echo " ${YELLOW}Could not fetch latest WordPress version (network issue)${NC}"
|
|
388
|
+
echo " ${YELLOW}Current version in composer.json: ${wp_version}${NC}"
|
|
389
|
+
fi
|
|
390
|
+
else
|
|
391
|
+
echo "${YELLOW}⚠️ NOT FOUND${NC}"
|
|
392
|
+
echo " ${YELLOW}WordPress core not found in composer.json${NC}"
|
|
393
|
+
fi
|
|
394
|
+
else
|
|
395
|
+
echo "${YELLOW}⚠️ SKIPPED${NC}"
|
|
396
|
+
echo " ${YELLOW}No composer.json found${NC}"
|
|
397
|
+
fi
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
echo ""
|
|
401
|
+
# Check if dependencies are up to date
|
|
402
|
+
echo "${CYAN}${PACKAGE} Checking dependencies...${NC}"
|
|
403
|
+
|
|
404
|
+
# Bedrock-specific: Check root composer.json
|
|
405
|
+
if [ "$CONTEXT" = "bedrock" ] && [ -f "composer.json" ]; then
|
|
406
|
+
printf " ${ARROW} Checking root composer dependencies... "
|
|
407
|
+
if [ ! -f "composer.lock" ]; then
|
|
408
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
409
|
+
echo " ${RED}composer.lock file missing${NC}"
|
|
410
|
+
echo " ${YELLOW}Please run:${NC}"
|
|
411
|
+
echo " ${BLUE}composer install${NC}"
|
|
412
|
+
exit 1
|
|
413
|
+
fi
|
|
414
|
+
if [ ! -d "vendor" ]; then
|
|
415
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
416
|
+
echo " ${RED}vendor directory missing${NC}"
|
|
417
|
+
echo " ${YELLOW}Please run:${NC}"
|
|
418
|
+
echo " ${BLUE}composer install${NC}"
|
|
419
|
+
exit 1
|
|
420
|
+
fi
|
|
421
|
+
# Check if composer.lock is valid by checking if it can be parsed
|
|
422
|
+
if ! composer validate --no-check-all --quiet >/dev/null 2>&1; then
|
|
423
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
424
|
+
echo " ${RED}composer.lock file is invalid or out of sync${NC}"
|
|
425
|
+
echo " ${YELLOW}Please run:${NC}"
|
|
426
|
+
echo " ${BLUE}composer update${NC}"
|
|
427
|
+
exit 1
|
|
428
|
+
fi
|
|
429
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
430
|
+
fi
|
|
431
|
+
|
|
432
|
+
# Check root package.json
|
|
433
|
+
if [ -f "package.json" ]; then
|
|
434
|
+
printf " ${ARROW} Checking root npm dependencies... "
|
|
435
|
+
if [ ! -f "package-lock.json" ]; then
|
|
436
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
437
|
+
echo " ${RED}package-lock.json file missing${NC}"
|
|
438
|
+
echo " ${YELLOW}Please run:${NC}"
|
|
439
|
+
echo " ${BLUE}nvm use${NC}"
|
|
440
|
+
echo " ${BLUE}npm install${NC}"
|
|
441
|
+
exit 1
|
|
442
|
+
fi
|
|
443
|
+
if [ ! -d "node_modules" ]; then
|
|
444
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
445
|
+
echo " ${RED}node_modules directory missing${NC}"
|
|
446
|
+
echo " ${YELLOW}Please run:${NC}"
|
|
447
|
+
echo " ${BLUE}nvm use${NC}"
|
|
448
|
+
echo " ${BLUE}npm install${NC}"
|
|
449
|
+
exit 1
|
|
450
|
+
fi
|
|
451
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
452
|
+
fi
|
|
453
|
+
|
|
454
|
+
# Bedrock-specific: Check all themes with package.json
|
|
455
|
+
if [ "$CONTEXT" = "bedrock" ] && [ -d "content/themes" ]; then
|
|
456
|
+
for theme_dir in content/themes/*/; do
|
|
457
|
+
if [ -f "${theme_dir}package.json" ]; then
|
|
458
|
+
theme_name=$(basename "$theme_dir")
|
|
459
|
+
printf " ${ARROW} Checking theme '${theme_name}' dependencies... "
|
|
460
|
+
if [ ! -f "${theme_dir}package-lock.json" ]; then
|
|
461
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
462
|
+
echo " ${RED}${theme_dir}package-lock.json file missing${NC}"
|
|
463
|
+
echo " ${YELLOW}Please run:${NC}"
|
|
464
|
+
echo " ${BLUE}cd ${theme_dir}${NC}"
|
|
465
|
+
echo " ${PURPLE}nvm use${NC}"
|
|
466
|
+
echo " ${PURPLE}npm install${NC}"
|
|
467
|
+
exit 1
|
|
468
|
+
fi
|
|
469
|
+
if [ ! -d "${theme_dir}node_modules" ]; then
|
|
470
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
471
|
+
echo " ${RED}${theme_dir}node_modules directory missing${NC}"
|
|
472
|
+
echo " ${YELLOW}Please run:${NC}"
|
|
473
|
+
echo " ${BLUE}cd ${theme_dir}${NC}"
|
|
474
|
+
echo " ${PURPLE}nvm use${NC}"
|
|
475
|
+
echo " ${PURPLE}npm install${NC}"
|
|
476
|
+
exit 1
|
|
477
|
+
fi
|
|
478
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
479
|
+
|
|
480
|
+
# Check for stylelint config in theme
|
|
481
|
+
printf " ${ARROW} Checking theme '${theme_name}' stylelint config... "
|
|
482
|
+
if ! [ -f "${theme_dir}.stylelintrc" ] && ! [ -f "${theme_dir}.stylelintrc.json" ] && ! [ -f "${theme_dir}.stylelintrc.js" ] && ! [ -f "${theme_dir}.stylelintrc.yml" ]; then
|
|
483
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
484
|
+
echo " ${RED}No .stylelintrc file found in theme directory${NC}"
|
|
485
|
+
echo " ${YELLOW}Please create stylelint config in theme:${NC}"
|
|
486
|
+
echo " ${BLUE}cd ${theme_dir}${NC}"
|
|
487
|
+
echo " ${BLUE}touch .stylelintrc.json${NC}"
|
|
488
|
+
echo " ${YELLOW}If moving from root, update these paths:${NC}"
|
|
489
|
+
echo " ${YELLOW}- csstools/value-no-unknown-custom-properties importFrom${NC}"
|
|
490
|
+
echo " ${YELLOW}- .github/workflows/styles.yml working directory${NC}"
|
|
491
|
+
exit 1
|
|
492
|
+
fi
|
|
493
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
494
|
+
fi
|
|
495
|
+
done
|
|
496
|
+
fi
|
|
497
|
+
|
|
498
|
+
# Standalone theme: Check for stylelint config in current directory
|
|
499
|
+
if [ "$CONTEXT" = "standalone" ]; then
|
|
500
|
+
printf " ${ARROW} Checking stylelint config... "
|
|
501
|
+
if ! [ -f ".stylelintrc" ] && ! [ -f ".stylelintrc.json" ] && ! [ -f ".stylelintrc.js" ] && ! [ -f ".stylelintrc.yml" ]; then
|
|
502
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
503
|
+
echo " ${RED}No .stylelintrc file found in theme directory${NC}"
|
|
504
|
+
echo " ${YELLOW}Please create stylelint config in theme:${NC}"
|
|
505
|
+
echo " ${BLUE}touch .stylelintrc.json${NC}"
|
|
506
|
+
exit 1
|
|
507
|
+
fi
|
|
508
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
509
|
+
fi
|
|
510
|
+
|
|
511
|
+
echo ""
|
|
512
|
+
echo "${CYAN}${SEARCH} Checking staged files for issues...${NC}"
|
|
513
|
+
|
|
514
|
+
# Get filtered staged files
|
|
515
|
+
staged_files=$(get_filtered_staged_files)
|
|
516
|
+
|
|
517
|
+
if [ -n "$staged_files" ]; then
|
|
518
|
+
# Check for merge conflict markers
|
|
519
|
+
printf " ${ARROW} Checking for merge conflict markers... "
|
|
520
|
+
conflict_files=""
|
|
521
|
+
for file in $staged_files; do
|
|
522
|
+
# Look for actual merge conflict markers at start of line, not in comments
|
|
523
|
+
if git show :"$file" 2>/dev/null | grep -qE "^<<<<<<<\s|^=======\s*$|^>>>>>>>\s"; then
|
|
524
|
+
conflict_files="$conflict_files $file"
|
|
525
|
+
fi
|
|
526
|
+
done
|
|
527
|
+
|
|
528
|
+
if [ -n "$conflict_files" ]; then
|
|
529
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
530
|
+
echo " ${RED}Merge conflict markers found in the following files:${NC}"
|
|
531
|
+
for file in $conflict_files; do
|
|
532
|
+
echo " ${YELLOW}• $file${NC}"
|
|
533
|
+
git show :"$file" | grep -nE "^<<<<<<<\s|^=======\s*$|^>>>>>>>\s" | head -3 | while read line; do
|
|
534
|
+
echo " ${PURPLE}$line${NC}"
|
|
535
|
+
done
|
|
536
|
+
done
|
|
537
|
+
echo " ${YELLOW}Please resolve conflicts and remove markers: ${NC}${PURPLE}<<<<<<< HEAD${NC}, ${PURPLE}=======${NC}, ${PURPLE}>>>>>>>${NC}"
|
|
538
|
+
exit 1
|
|
539
|
+
fi
|
|
540
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
541
|
+
|
|
542
|
+
# Check for scissor marks in all staged files
|
|
543
|
+
printf " ${ARROW} Checking for scissor marks... "
|
|
544
|
+
scissor_files=""
|
|
545
|
+
for file in $staged_files; do
|
|
546
|
+
if git show :"$file" 2>/dev/null | grep -q -- "------8<----------\|-8<-"; then
|
|
547
|
+
scissor_files="$scissor_files $file"
|
|
548
|
+
fi
|
|
549
|
+
done
|
|
550
|
+
|
|
551
|
+
if [ -n "$scissor_files" ]; then
|
|
552
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
553
|
+
echo " ${RED}Found scissor marks in the following files:${NC}"
|
|
554
|
+
for file in $scissor_files; do
|
|
555
|
+
echo " ${YELLOW}• $file${NC}"
|
|
556
|
+
git show :"$file" | grep -n -- "------8<----------\|-8<-" | head -3 | while read line; do
|
|
557
|
+
echo " ${RED}$line${NC}"
|
|
558
|
+
done
|
|
559
|
+
done
|
|
560
|
+
echo " ${YELLOW}Please remove all scissor marks (------8<---------- or -8<-) before committing${NC}"
|
|
561
|
+
exit 1
|
|
562
|
+
fi
|
|
563
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
564
|
+
else
|
|
565
|
+
echo " ${YELLOW}No files to check${NC}"
|
|
566
|
+
fi
|
|
567
|
+
|
|
568
|
+
echo ""
|
|
569
|
+
echo "${CYAN}${CLEAN} Running code quality checks...${NC}"
|
|
570
|
+
|
|
571
|
+
# Check for PHP CodeSniffer errors
|
|
572
|
+
php_files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' | tr '\n' ' ')
|
|
573
|
+
if [ -n "$php_files" ] && [ -f "phpcs.xml" ]; then
|
|
574
|
+
printf " ${ARROW} Running PHP CodeSniffer... "
|
|
575
|
+
if [ -f "./vendor/bin/phpcs" ]; then
|
|
576
|
+
if ! ./vendor/bin/phpcs --standard=phpcs.xml --error-severity=1 --warning-severity=8 --colors $php_files; then
|
|
577
|
+
echo ""
|
|
578
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
579
|
+
echo " ${YELLOW}To fix automatically:${NC}"
|
|
580
|
+
echo " ${BLUE}./vendor/bin/phpcbf --standard=phpcs.xml $php_files${NC}"
|
|
581
|
+
exit 1
|
|
582
|
+
fi
|
|
583
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
584
|
+
else
|
|
585
|
+
echo "${YELLOW}⚠️ SKIPPED${NC}"
|
|
586
|
+
echo " ${YELLOW}phpcs not found in vendor/bin${NC}"
|
|
587
|
+
fi
|
|
588
|
+
fi
|
|
589
|
+
|
|
590
|
+
# Check SCSS files with stylelint (changed files only)
|
|
591
|
+
scss_files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.scss$' | tr '\n' ' ')
|
|
592
|
+
if [ -n "$scss_files" ]; then
|
|
593
|
+
printf " ${ARROW} Running stylelint on changed SCSS files... "
|
|
594
|
+
|
|
595
|
+
# Find stylelint config
|
|
596
|
+
stylelint_config=""
|
|
597
|
+
if [ "$CONTEXT" = "bedrock" ]; then
|
|
598
|
+
# Find stylelint config in theme directories
|
|
599
|
+
for theme_dir in content/themes/*/; do
|
|
600
|
+
if [ -f "${theme_dir}.stylelintrc" ]; then
|
|
601
|
+
stylelint_config="${theme_dir}.stylelintrc"
|
|
602
|
+
break
|
|
603
|
+
elif [ -f "${theme_dir}.stylelintrc.json" ]; then
|
|
604
|
+
stylelint_config="${theme_dir}.stylelintrc.json"
|
|
605
|
+
break
|
|
606
|
+
fi
|
|
607
|
+
done
|
|
608
|
+
else
|
|
609
|
+
# Standalone: check current directory
|
|
610
|
+
if [ -f ".stylelintrc" ]; then
|
|
611
|
+
stylelint_config=".stylelintrc"
|
|
612
|
+
elif [ -f ".stylelintrc.json" ]; then
|
|
613
|
+
stylelint_config=".stylelintrc.json"
|
|
614
|
+
elif [ -f ".stylelintrc.js" ]; then
|
|
615
|
+
stylelint_config=".stylelintrc.js"
|
|
616
|
+
elif [ -f ".stylelintrc.yml" ]; then
|
|
617
|
+
stylelint_config=".stylelintrc.yml"
|
|
618
|
+
fi
|
|
619
|
+
fi
|
|
620
|
+
|
|
621
|
+
if [ -n "$stylelint_config" ]; then
|
|
622
|
+
if ! npx stylelint --config "$stylelint_config" $scss_files 2>/dev/null; then
|
|
623
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
624
|
+
echo " ${RED}Stylelint found errors in SCSS files${NC}"
|
|
625
|
+
echo " ${YELLOW}Please fix the following files:${NC}"
|
|
626
|
+
for file in $scss_files; do
|
|
627
|
+
echo " ${YELLOW}• $file${NC}"
|
|
628
|
+
done
|
|
629
|
+
echo " ${YELLOW}To see detailed errors:${NC}"
|
|
630
|
+
echo " ${BLUE}npx stylelint --config \"$stylelint_config\" $scss_files${NC}"
|
|
631
|
+
exit 1
|
|
632
|
+
fi
|
|
633
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
634
|
+
else
|
|
635
|
+
echo "${YELLOW}⚠️ SKIPPED${NC}"
|
|
636
|
+
echo " ${YELLOW}No .stylelintrc config found${NC}"
|
|
637
|
+
fi
|
|
638
|
+
else
|
|
639
|
+
echo " ${YELLOW}No SCSS files to check${NC}"
|
|
640
|
+
fi
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
# Check PHP 8.3 compatibility
|
|
644
|
+
if [ "$CHECK_CHANGED_ONLY" = "true" ]; then
|
|
645
|
+
# Check only changed PHP files
|
|
646
|
+
php_files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' | tr '\n' ' ')
|
|
647
|
+
if [ -n "$php_files" ]; then
|
|
648
|
+
printf " ${ARROW} Checking PHP 8.3 compatibility on changed files... "
|
|
649
|
+
php_syntax_output=""
|
|
650
|
+
php_syntax_exit_code=0
|
|
651
|
+
|
|
652
|
+
for file in $php_files; do
|
|
653
|
+
if [ -f "$file" ]; then
|
|
654
|
+
file_output=$(php -l "$file" 2>&1)
|
|
655
|
+
file_exit_code=$?
|
|
656
|
+
if [ $file_exit_code -ne 0 ]; then
|
|
657
|
+
php_syntax_output="$php_syntax_output\n$file_output"
|
|
658
|
+
php_syntax_exit_code=1
|
|
659
|
+
fi
|
|
660
|
+
fi
|
|
661
|
+
done
|
|
662
|
+
|
|
663
|
+
if [ $php_syntax_exit_code -ne 0 ]; then
|
|
664
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
665
|
+
echo " ${PURPLE}PHP syntax errors found:${NC}"
|
|
666
|
+
echo "$php_syntax_output" | while IFS= read -r line; do
|
|
667
|
+
if [ -n "$line" ]; then
|
|
668
|
+
echo " ${YELLOW}$line${NC}"
|
|
669
|
+
fi
|
|
670
|
+
done
|
|
671
|
+
echo ""
|
|
672
|
+
echo " ${YELLOW}To check syntax manually:${NC}"
|
|
673
|
+
echo " ${BLUE}php -l [filename]${NC}"
|
|
674
|
+
exit 1
|
|
675
|
+
fi
|
|
676
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
677
|
+
else
|
|
678
|
+
echo " ${YELLOW}No PHP files to check${NC}"
|
|
679
|
+
fi
|
|
680
|
+
else
|
|
681
|
+
# Check entire codebase (original behavior)
|
|
682
|
+
printf " ${ARROW} Checking PHP 8.3 compatibility on entire codebase... "
|
|
683
|
+
if [ "$CONTEXT" = "bedrock" ]; then
|
|
684
|
+
php_syntax_output=$(find -L . -name '*.php' -not -path "./vendor/*" -not -path "./node_modules/*" -not -path "./src/*" -not -path "./js/*" -not -path "./css/*" -not -path "./sass/*" -not -path "./plugin-update-checker/*" -not -path "./content/themes/*" -not -path "./content/plugins/*" -not -path "./content/uploads/*" -not -path "./content/languages/*" -not -path "./content-mu-plugins/*" -not -path "./media/*" -not -path "./wp/*" -exec php -l {} \; 2>&1)
|
|
685
|
+
else
|
|
686
|
+
php_syntax_output=$(find -L . -name '*.php' -not -path "./vendor/*" -not -path "./node_modules/*" -exec php -l {} \; 2>&1)
|
|
687
|
+
fi
|
|
688
|
+
php_syntax_exit_code=$?
|
|
689
|
+
|
|
690
|
+
if [ $php_syntax_exit_code -ne 0 ]; then
|
|
691
|
+
echo "${RED}${CROSS} FAILED${NC}"
|
|
692
|
+
echo " ${PURPLE}PHP syntax errors found:${NC}"
|
|
693
|
+
|
|
694
|
+
if [ -n "$php_syntax_output" ]; then
|
|
695
|
+
echo "$php_syntax_output" | while IFS= read -r line; do
|
|
696
|
+
if [ -n "$line" ]; then
|
|
697
|
+
echo " ${YELLOW}$line${NC}"
|
|
698
|
+
fi
|
|
699
|
+
done
|
|
700
|
+
fi
|
|
701
|
+
|
|
702
|
+
echo ""
|
|
703
|
+
echo " ${YELLOW}To check syntax manually:${NC}"
|
|
704
|
+
echo " ${BLUE}php -l [filename]${NC}"
|
|
705
|
+
exit 1
|
|
706
|
+
fi
|
|
707
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
708
|
+
fi
|
|
709
|
+
|
|
710
|
+
echo ""
|
|
711
|
+
echo "${CYAN}${SEARCH} Running comprehensive codebase checks...${NC}"
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
# Check GitHub Actions workflow syntax
|
|
715
|
+
printf " ${ARROW} Checking GitHub Actions workflow syntax... "
|
|
716
|
+
workflow_files=""
|
|
717
|
+
if [ -d ".github/workflows" ]; then
|
|
718
|
+
workflow_files=$(find .github/workflows -name "*.yml" -o -name "*.yaml" 2>/dev/null)
|
|
719
|
+
fi
|
|
720
|
+
|
|
721
|
+
if [ -n "$workflow_files" ]; then
|
|
722
|
+
syntax_errors=""
|
|
723
|
+
for workflow in $workflow_files; do
|
|
724
|
+
# Basic YAML syntax check using python if available
|
|
725
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
726
|
+
if ! python3 -c "import yaml; yaml.safe_load(open('$workflow'))" >/dev/null 2>&1; then
|
|
727
|
+
syntax_errors="$syntax_errors $workflow"
|
|
728
|
+
fi
|
|
729
|
+
fi
|
|
730
|
+
done
|
|
731
|
+
echo "${GREEN}${CHECK} OK${NC}"
|
|
732
|
+
else
|
|
733
|
+
echo "${YELLOW}⚠️ NOT FOUND${NC}"
|
|
734
|
+
echo " ${YELLOW}No GitHub Actions workflows found${NC}"
|
|
735
|
+
fi
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
# Bedrock-specific: Check PHP files with PHPCS on entire codebase
|
|
739
|
+
if [ "$CONTEXT" = "bedrock" ] && [ -f "phpcs.xml" ] && [ -f "./vendor/bin/phpcs" ]; then
|
|
740
|
+
echo " ${ARROW} Running PHP CodeSniffer on entire codebase..."
|
|
741
|
+
# Use the same pattern as your GitHub Actions workflow
|
|
742
|
+
if ./vendor/bin/phpcs -p . --extensions=php --ignore=vendor,content/themes,content/plugins,content/uploads,node_modules,src,js,css,sass,plugin-update-checker,wp,mu-plugins/air-blocks-acf-example-data,content/languages,scripts/Roots/Bedrock/Installer.php,index.php,content/mu-plugins --standard=phpcs.xml; then
|
|
743
|
+
echo " ${GREEN}${CHECK} OK${NC}"
|
|
744
|
+
else
|
|
745
|
+
echo " ${RED}${CROSS} FAILED${NC}"
|
|
746
|
+
echo ""
|
|
747
|
+
echo " ${YELLOW}To fix automatically:${NC}"
|
|
748
|
+
echo " ${BLUE}./vendor/bin/phpcbf -p . --extensions=php --ignore=vendor,content/themes,content/plugins,content/uploads,node_modules,src,js,css,sass,plugin-update-checker,wp,mu-plugins/air-blocks-acf-example-data,content/languages,scripts/Roots/Bedrock/Installer.php,index.php,content/mu-plugins --standard=phpcs.xml${NC}"
|
|
749
|
+
exit 1
|
|
750
|
+
fi
|
|
751
|
+
fi
|
|
752
|
+
|
|
753
|
+
echo ""
|
|
754
|
+
|
|
755
|
+
echo ""
|
|
756
|
+
echo "${GREEN}${CHECK} All pre-commit checks passed!${NC}"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Pre-commit hook configuration
|
|
2
|
+
# Directories to exclude (pipe-separated for easier parsing)
|
|
3
|
+
EXCLUDE_DIRS=".husky|node_modules|vendor|.git|uploads|cache"
|
|
4
|
+
|
|
5
|
+
# Files to exclude (pipe-separated, supports glob patterns)
|
|
6
|
+
EXCLUDE_FILES="*.min.js|*.min.css|*.map|*.lock|*.log|composer.lock|package-lock.json|yarn.lock"
|
|
7
|
+
|
|
8
|
+
# File extensions to exclude from conflict/scissor checks
|
|
9
|
+
EXCLUDE_EXTENSIONS=".jpg|.jpeg|.png|.gif|.svg|.ico|.pdf|.zip|.tar|.gz|.woff|.woff2|.ttf|.eot"
|
|
10
|
+
|
|
11
|
+
# Quality checks configuration
|
|
12
|
+
# CHECK_CHANGED_ONLY: true = only check changed files, false = check entire codebase
|
|
13
|
+
CHECK_CHANGED_ONLY=true
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Code quality checks
|
|
2
|
+
|
|
3
|
+
[](#)  [](https://www.npmjs.com/package/@digitoimistodude/code-quality-checks)
|
|
4
|
+
|
|
5
|
+
Dude's comprehensive code quality definitions and pre-commit hooks for WordPress projects.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
* Pre-commit hooks with comprehensive code quality checks
|
|
10
|
+
* Automatic context detection (standalone theme vs Bedrock project)
|
|
11
|
+
* PHP CodeSniffer validation
|
|
12
|
+
* Stylelint for SCSS files
|
|
13
|
+
* WordPress version checks (Bedrock mode)
|
|
14
|
+
* Dependency validation for Composer and npm
|
|
15
|
+
* Merge conflict and scissor mark detection
|
|
16
|
+
* Commit message validation with Linear integration
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @digitoimistodude/code-quality-checks husky --save-dev
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Initialize husky:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx husky init
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Setup
|
|
31
|
+
|
|
32
|
+
Create wrapper scripts in your project's `.husky/` directory that reference the package hooks.
|
|
33
|
+
|
|
34
|
+
### .husky/pre-commit
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
node_modules/@digitoimistodude/code-quality-checks/.husky/pre-commit
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### .husky/commit-msg
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
node_modules/@digitoimistodude/code-quality-checks/.husky/commit-msg "$1"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Make them executable:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
chmod +x .husky/pre-commit .husky/commit-msg
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Context detection
|
|
53
|
+
|
|
54
|
+
The hooks automatically detect whether they're running in:
|
|
55
|
+
|
|
56
|
+
* **Bedrock project**: Has `content/themes/` directory and `composer.json`
|
|
57
|
+
* **Standalone theme**: Everything else
|
|
58
|
+
|
|
59
|
+
### Bedrock mode checks
|
|
60
|
+
|
|
61
|
+
* WordPress version in composer.json
|
|
62
|
+
* Composer dependencies
|
|
63
|
+
* CHANGELOG.md format and freshness
|
|
64
|
+
* Theme style.css validation
|
|
65
|
+
* Multi-theme dependency checks
|
|
66
|
+
* Root stylelintrc should not exist
|
|
67
|
+
|
|
68
|
+
### Standalone mode checks
|
|
69
|
+
|
|
70
|
+
* Basic file requirements (.nvmrc, phpcs.xml, gulpfile.js)
|
|
71
|
+
* npm dependencies
|
|
72
|
+
* Stylelint config in current directory
|
|
73
|
+
* PHP syntax validation
|
|
74
|
+
* SCSS linting
|
|
75
|
+
|
|
76
|
+
## Configuration
|
|
77
|
+
|
|
78
|
+
Create `.husky/pre-commit-config` in your project to override defaults:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Directories to exclude
|
|
82
|
+
EXCLUDE_DIRS=".husky|node_modules|vendor|.git|uploads|cache"
|
|
83
|
+
|
|
84
|
+
# Files to exclude (glob patterns)
|
|
85
|
+
EXCLUDE_FILES="*.min.js|*.min.css|*.map|*.lock|*.log"
|
|
86
|
+
|
|
87
|
+
# File extensions to exclude
|
|
88
|
+
EXCLUDE_EXTENSIONS=".jpg|.jpeg|.png|.gif|.svg|.ico|.pdf"
|
|
89
|
+
|
|
90
|
+
# Check only changed files (true) or entire codebase (false)
|
|
91
|
+
CHECK_CHANGED_ONLY=true
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Requirements
|
|
95
|
+
|
|
96
|
+
* Node.js >= 18.0.0
|
|
97
|
+
* PHP >= 8.1
|
|
98
|
+
* Composer (for Bedrock projects)
|
|
99
|
+
* husky >= 9.1.7
|
|
100
|
+
|
|
101
|
+
## Updating
|
|
102
|
+
|
|
103
|
+
Simply update the package to get the latest hooks:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm update @digitoimistodude/code-quality-checks
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
No need to re-copy files - your wrapper scripts always use the latest version from node_modules.
|
package/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @digitoimistodude/code-quality-checks
|
|
3
|
+
*
|
|
4
|
+
* Exports the path to husky hooks for direct reference.
|
|
5
|
+
*
|
|
6
|
+
* Usage in your project's .husky/pre-commit:
|
|
7
|
+
* node_modules/@digitoimistodude/code-quality-checks/.husky/pre-commit
|
|
8
|
+
*
|
|
9
|
+
* Or import the path:
|
|
10
|
+
* const { huskyPath } = require('@digitoimistodude/code-quality-checks');
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
huskyPath: path.join(__dirname, '.husky'),
|
|
17
|
+
preCommitPath: path.join(__dirname, '.husky', 'pre-commit'),
|
|
18
|
+
preCommitConfigPath: path.join(__dirname, '.husky', 'pre-commit-config'),
|
|
19
|
+
commitMsgPath: path.join(__dirname, '.husky', 'commit-msg')
|
|
20
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@digitoimistodude/code-quality-checks",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Code quality definitions and Husky pre-commit hooks for Dude WordPress projects",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
".husky/pre-commit",
|
|
8
|
+
".husky/pre-commit-config",
|
|
9
|
+
".husky/commit-msg",
|
|
10
|
+
"index.js"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/digitoimistodude/code-quality-checks.git"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/digitoimistodude/code-quality-checks/issues"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/digitoimistodude/code-quality-checks#readme",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"husky": "^9.1.7"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"code-quality",
|
|
28
|
+
"pre-commit",
|
|
29
|
+
"husky",
|
|
30
|
+
"php",
|
|
31
|
+
"wordpress",
|
|
32
|
+
"stylelint",
|
|
33
|
+
"phpcs",
|
|
34
|
+
"dude"
|
|
35
|
+
],
|
|
36
|
+
"author": "Digitoimisto Dude Oy <koodarit@dude.fi>",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
41
|
+
}
|