@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.
@@ -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
+ [![husky](https://img.shields.io/badge/husky-a8b1ff?style=for-the-badge)](#) ![Version](https://img.shields.io/badge/version-2.0.0-blue?style=for-the-badge) [![npm](https://img.shields.io/npm/v/@digitoimistodude/code-quality-checks?style=for-the-badge)](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
+ }