|
1 | 1 | #!/usr/bin/env bash |
2 | 2 | # |
3 | 3 | # WP Code Check by Hypercart - Performance Analysis Script |
4 | | -# Version: 2.0.15 |
| 4 | +# Version: 2.2.0 |
5 | 5 | # |
6 | 6 | # Fast, zero-dependency WordPress performance analyzer |
7 | 7 | # Catches critical issues before they crash your site |
@@ -81,7 +81,7 @@ source "$REPO_ROOT/lib/pattern-loader.sh" |
81 | 81 | # This is the ONLY place the version number should be defined. |
82 | 82 | # All other references (logs, JSON, banners) use this variable. |
83 | 83 | # Update this ONE line when bumping versions - never hardcode elsewhere. |
84 | | -SCRIPT_VERSION="2.1.0" |
| 84 | +SCRIPT_VERSION="2.2.1" |
85 | 85 |
|
86 | 86 | # Get the start/end line range for the enclosing function/method. |
87 | 87 | # |
@@ -1024,8 +1024,82 @@ detect_project_info() { |
1024 | 1024 | project_type="fixture" |
1025 | 1025 | project_name=$(basename "$scan_path") |
1026 | 1026 | else |
1027 | | - # Generic project |
| 1027 | + # Generic project - detect from package.json or file types |
1028 | 1028 | project_name=$(basename "$scan_path") |
| 1029 | + |
| 1030 | + # Check for package.json (Node.js/JS projects) |
| 1031 | + local pkg_json="" |
| 1032 | + if [ -f "$scan_path/package.json" ]; then |
| 1033 | + pkg_json="$scan_path/package.json" |
| 1034 | + elif [ -f "$(dirname "$scan_path")/package.json" ]; then |
| 1035 | + pkg_json="$(dirname "$scan_path")/package.json" |
| 1036 | + fi |
| 1037 | + |
| 1038 | + if [ -n "$pkg_json" ]; then |
| 1039 | + # Build comma-separated type list from detected frameworks |
| 1040 | + local detected_types="" |
| 1041 | + |
| 1042 | + # Base: it's a Node.js project |
| 1043 | + detected_types="nodejs" |
| 1044 | + |
| 1045 | + # Detect TypeScript |
| 1046 | + if grep -qE '"typescript"|"ts-node"' "$pkg_json" 2>/dev/null; then |
| 1047 | + detected_types="$detected_types, typescript" |
| 1048 | + fi |
| 1049 | + |
| 1050 | + # Detect React |
| 1051 | + if grep -qE '"react"[[:space:]]*:' "$pkg_json" 2>/dev/null; then |
| 1052 | + detected_types="$detected_types, react" |
| 1053 | + fi |
| 1054 | + |
| 1055 | + # Detect Next.js (after React, as Next includes React) |
| 1056 | + if grep -qE '"next"[[:space:]]*:' "$pkg_json" 2>/dev/null; then |
| 1057 | + detected_types="$detected_types, nextjs" |
| 1058 | + fi |
| 1059 | + |
| 1060 | + # Detect Vue.js |
| 1061 | + if grep -qE '"vue"[[:space:]]*:' "$pkg_json" 2>/dev/null; then |
| 1062 | + detected_types="$detected_types, vue" |
| 1063 | + fi |
| 1064 | + |
| 1065 | + # Detect Nuxt (Vue's Next.js equivalent) |
| 1066 | + if grep -qE '"nuxt"[[:space:]]*:' "$pkg_json" 2>/dev/null; then |
| 1067 | + detected_types="$detected_types, nuxt" |
| 1068 | + fi |
| 1069 | + |
| 1070 | + # Detect Express.js |
| 1071 | + if grep -qE '"express"[[:space:]]*:' "$pkg_json" 2>/dev/null; then |
| 1072 | + detected_types="$detected_types, express" |
| 1073 | + fi |
| 1074 | + |
| 1075 | + project_type="$detected_types" |
| 1076 | + |
| 1077 | + # Extract name/version from package.json if not already set |
| 1078 | + if [ "$project_name" = "Unknown" ] || [ -z "$project_name" ]; then |
| 1079 | + project_name=$(grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg_json" 2>/dev/null | head -1 | cut -d'"' -f4) |
| 1080 | + fi |
| 1081 | + if [ -z "$project_version" ]; then |
| 1082 | + project_version=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg_json" 2>/dev/null | head -1 | cut -d'"' -f4) |
| 1083 | + fi |
| 1084 | + if [ -z "$project_description" ]; then |
| 1085 | + project_description=$(grep -o '"description"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg_json" 2>/dev/null | head -1 | cut -d'"' -f4) |
| 1086 | + fi |
| 1087 | + if [ -z "$project_author" ]; then |
| 1088 | + project_author=$(grep -o '"author"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg_json" 2>/dev/null | head -1 | cut -d'"' -f4) |
| 1089 | + fi |
| 1090 | + elif [ -d "$scan_path" ]; then |
| 1091 | + # No package.json - detect by file presence |
| 1092 | + local has_js=$(find "$scan_path" -maxdepth 2 \( -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" \) -type f 2>/dev/null | head -1) |
| 1093 | + local has_php=$(find "$scan_path" -maxdepth 2 -name "*.php" -type f 2>/dev/null | head -1) |
| 1094 | + |
| 1095 | + if [ -n "$has_js" ] && [ -n "$has_php" ]; then |
| 1096 | + project_type="php, javascript" |
| 1097 | + elif [ -n "$has_js" ]; then |
| 1098 | + project_type="javascript" |
| 1099 | + elif [ -n "$has_php" ]; then |
| 1100 | + project_type="php" |
| 1101 | + fi |
| 1102 | + fi |
1029 | 1103 | fi |
1030 | 1104 | fi |
1031 | 1105 |
|
@@ -1194,6 +1268,8 @@ if [ "$ENABLE_LOGGING" = true ]; then |
1194 | 1268 | theme) type_display_log="WordPress Theme" ;; |
1195 | 1269 | fixture) type_display_log="Fixture Test" ;; |
1196 | 1270 | unknown) type_display_log="Unknown" ;; |
| 1271 | + # New types pass through as-is (already descriptive, e.g., "nodejs, react, nextjs") |
| 1272 | + nodejs*|javascript*|php*|react*|vue*|typescript*) type_display_log="$PROJECT_TYPE_LOG" ;; |
1197 | 1273 | esac |
1198 | 1274 | echo "Type: $type_display_log" |
1199 | 1275 | if [ -n "$PROJECT_AUTHOR_LOG" ]; then |
@@ -1633,6 +1709,8 @@ generate_html_report() { |
1633 | 1709 | theme) type_display="WordPress Theme" ;; |
1634 | 1710 | fixture) type_display="Fixture Test" ;; |
1635 | 1711 | unknown) type_display="Unknown" ;; |
| 1712 | + # New types pass through as-is (already descriptive, e.g., "nodejs, react, nextjs") |
| 1713 | + nodejs*|javascript*|php*|react*|vue*|typescript*) type_display="$project_type" ;; |
1636 | 1714 | esac |
1637 | 1715 |
|
1638 | 1716 | project_info_html="<div style='font-size: 1.1em; font-weight: 600; margin-bottom: 5px;'>PROJECT INFORMATION</div>" |
@@ -3023,11 +3101,18 @@ run_check() { |
3023 | 3101 | text_echo " PATHS: $PATHS" |
3024 | 3102 | fi |
3025 | 3103 |
|
3026 | | - # PERFORMANCE: Use cached file list instead of grep -r |
3027 | | - if [ "$PHP_FILE_COUNT" -eq 1 ]; then |
3028 | | - result=$(grep -Hn $include_args $patterns "$PHP_FILE_LIST" 2>/dev/null) || true |
| 3104 | + # PERFORMANCE: Use cached file list instead of grep -r when available. |
| 3105 | + # When there are no PHP files (e.g., JS/Node-only projects), fall back |
| 3106 | + # to recursive grep over the original paths so JS/headless patterns |
| 3107 | + # still run. |
| 3108 | + if [ "$PHP_FILE_COUNT" -gt 0 ] && [ -n "$PHP_FILE_LIST" ] && [ -f "$PHP_FILE_LIST" ]; then |
| 3109 | + if [ "$PHP_FILE_COUNT" -eq 1 ]; then |
| 3110 | + result=$(grep -Hn $include_args $patterns "$PHP_FILE_LIST" 2>/dev/null) || true |
| 3111 | + else |
| 3112 | + result=$(cat "$PHP_FILE_LIST" | xargs grep -Hn $include_args $patterns 2>/dev/null) || true |
| 3113 | + fi |
3029 | 3114 | else |
3030 | | - result=$(cat "$PHP_FILE_LIST" | xargs grep -Hn $include_args $patterns 2>/dev/null) || true |
| 3115 | + result=$(grep -rHn $EXCLUDE_ARGS $include_args $patterns "$PATHS" 2>/dev/null) || true |
3031 | 3116 | fi |
3032 | 3117 |
|
3033 | 3118 | if [ -n "$result" ]; then |
@@ -3157,16 +3242,17 @@ else |
3157 | 3242 | PHP_FILE_COUNT=$(wc -l < "$PHP_FILE_LIST_CACHE" | tr -d ' ') |
3158 | 3243 |
|
3159 | 3244 | if [ "$PHP_FILE_COUNT" -eq 0 ]; then |
| 3245 | + # Relaxed PHP gate: it's valid to have JS/Node-only projects. |
| 3246 | + # We log for debugging but do not exit, so JS/Node/Headless checks can still run. |
3160 | 3247 | debug_echo "No PHP files found in: $PATHS" |
3161 | 3248 | rm -f "$PHP_FILE_LIST_CACHE" |
3162 | | - echo "Error: No PHP files found in: $PATHS" |
3163 | | - exit 1 |
3164 | | - fi |
3165 | | - |
3166 | | - debug_echo "Cached $PHP_FILE_COUNT PHP files" |
| 3249 | + PHP_FILE_LIST="" |
| 3250 | + else |
| 3251 | + debug_echo "Cached $PHP_FILE_COUNT PHP files" |
3167 | 3252 |
|
3168 | | - # Export for use in grep commands |
3169 | | - PHP_FILE_LIST="$PHP_FILE_LIST_CACHE" |
| 3253 | + # Export for use in grep commands |
| 3254 | + PHP_FILE_LIST="$PHP_FILE_LIST_CACHE" |
| 3255 | + fi |
3170 | 3256 | fi |
3171 | 3257 |
|
3172 | 3258 | # Cleanup function to remove cache on exit |
@@ -3256,13 +3342,19 @@ cached_grep() { |
3256 | 3342 | fi |
3257 | 3343 | done |
3258 | 3344 |
|
3259 | | - # If single file mode, just use regular grep |
3260 | | - if [ "$PHP_FILE_COUNT" -eq 1 ]; then |
| 3345 | + # If we have a cached PHP file list, use it; otherwise fall back to |
| 3346 | + # recursive grep on the original paths. This lets JS/Node-only repos |
| 3347 | + # (no PHP files) still be scanned safely without depending on the |
| 3348 | + # PHP_FILE_LIST cache. |
| 3349 | + if [ "$PHP_FILE_COUNT" -eq 1 ] && [ -n "$PHP_FILE_LIST" ] && [ -f "$PHP_FILE_LIST" ]; then |
3261 | 3350 | grep -Hn "${grep_args[@]}" "$pattern" "$PHP_FILE_LIST" 2>/dev/null || true |
3262 | | - else |
| 3351 | + elif [ "$PHP_FILE_COUNT" -gt 1 ] && [ -n "$PHP_FILE_LIST" ] && [ -f "$PHP_FILE_LIST" ]; then |
3263 | 3352 | # Use cached file list with xargs for parallel processing |
3264 | 3353 | # -Hn adds filename and line number (like -rHn but without recursion) |
3265 | 3354 | cat "$PHP_FILE_LIST" | xargs grep -Hn "${grep_args[@]}" "$pattern" 2>/dev/null || true |
| 3355 | + else |
| 3356 | + # No PHP cache (e.g., JS-only project). Fall back to recursive grep. |
| 3357 | + grep -rHn "${grep_args[@]}" "$pattern" "$PATHS" 2>/dev/null || true |
3266 | 3358 | fi |
3267 | 3359 | } |
3268 | 3360 |
|
@@ -3349,7 +3441,9 @@ SUPERGLOBAL_VISIBLE="" |
3349 | 3441 |
|
3350 | 3442 | # Find all superglobal manipulation patterns |
3351 | 3443 | # PERFORMANCE: Use cached file list instead of grep -r |
3352 | | -SUPERGLOBAL_MATCHES=$(cached_grep -E "unset\\(\\$_(GET|POST|REQUEST|COOKIE)\\[|\\$_(GET|POST|REQUEST)[[:space:]]*=|\\$_(GET|POST|REQUEST|COOKIE)\\[[^]]*\\][[:space:]]*=" | \ |
| 3444 | +# NOTE: Explicitly restrict to PHP files so that documentation (e.g. .md) and |
| 3445 | +# non-PHP assets are not scanned when running in JS-only or mixed repos. |
| 3446 | +SUPERGLOBAL_MATCHES=$(cached_grep --include=*.php -E "unset\\(\\$_(GET|POST|REQUEST|COOKIE)\\[|\\$_(GET|POST|REQUEST)[[:space:]]*=|\\$_(GET|POST|REQUEST|COOKIE)\\[[^]]*\\][[:space:]]*=" | \ |
3353 | 3447 | grep -v '//.*\$_' || true) |
3354 | 3448 |
|
3355 | 3449 | if [ -n "$SUPERGLOBAL_MATCHES" ]; then |
@@ -3515,7 +3609,9 @@ UNSANITIZED_VISIBLE="" |
3515 | 3609 | # Note: We do NOT exclude isset/empty here because they don't sanitize - they only check existence |
3516 | 3610 | # We'll filter those out in a more sophisticated way below |
3517 | 3611 | # PERFORMANCE: Use cached file list instead of grep -r |
3518 | | -UNSANITIZED_MATCHES=$(cached_grep -E '\$_(GET|POST|REQUEST)\[' | \ |
| 3612 | +# NOTE: Restrict to PHP files explicitly; in JS-only repos the fallback path in |
| 3613 | +# cached_grep will otherwise recurse into documentation and non-PHP assets. |
| 3614 | +UNSANITIZED_MATCHES=$(cached_grep --include=*.php -E '\$_(GET|POST|REQUEST)\[' | \ |
3519 | 3615 | grep -v 'sanitize_' | \ |
3520 | 3616 | grep -v 'esc_' | \ |
3521 | 3617 | grep -v 'absint' | \ |
|
0 commit comments