mcp-wordpress 2.6.3 → 2.7.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/README.md +1 -1
- package/dist/cache/CacheInvalidation.d.ts +25 -6
- package/dist/cache/CacheInvalidation.d.ts.map +1 -1
- package/dist/cache/CacheInvalidation.js +168 -16
- package/dist/cache/CacheInvalidation.js.map +1 -1
- package/dist/cache/HttpCacheWrapper.d.ts.map +1 -1
- package/dist/cache/HttpCacheWrapper.js +3 -4
- package/dist/cache/HttpCacheWrapper.js.map +1 -1
- package/dist/cache/SEOCacheManager.d.ts +150 -0
- package/dist/cache/SEOCacheManager.d.ts.map +1 -0
- package/dist/cache/SEOCacheManager.js +275 -0
- package/dist/cache/SEOCacheManager.js.map +1 -0
- package/dist/client/SEOWordPressClient.d.ts +164 -0
- package/dist/client/SEOWordPressClient.d.ts.map +1 -0
- package/dist/client/SEOWordPressClient.js +674 -0
- package/dist/client/SEOWordPressClient.js.map +1 -0
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/api.js +50 -20
- package/dist/client/api.js.map +1 -1
- package/dist/client/auth.js +19 -19
- package/dist/client/auth.js.map +1 -1
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +14 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/managers/AuthManager.d.ts +39 -0
- package/dist/client/managers/AuthManager.d.ts.map +1 -0
- package/dist/client/managers/AuthManager.js +142 -0
- package/dist/client/managers/AuthManager.js.map +1 -0
- package/dist/client/managers/AuthenticationManager.d.ts.map +1 -1
- package/dist/client/managers/AuthenticationManager.js +10 -9
- package/dist/client/managers/AuthenticationManager.js.map +1 -1
- package/dist/client/managers/BaseManager.d.ts.map +1 -1
- package/dist/client/managers/BaseManager.js +12 -0
- package/dist/client/managers/BaseManager.js.map +1 -1
- package/dist/client/managers/ComposedAuthenticationManager.d.ts +94 -0
- package/dist/client/managers/ComposedAuthenticationManager.d.ts.map +1 -0
- package/dist/client/managers/ComposedAuthenticationManager.js +340 -0
- package/dist/client/managers/ComposedAuthenticationManager.js.map +1 -0
- package/dist/client/managers/ComposedManagerFactory.d.ts +104 -0
- package/dist/client/managers/ComposedManagerFactory.d.ts.map +1 -0
- package/dist/client/managers/ComposedManagerFactory.js +180 -0
- package/dist/client/managers/ComposedManagerFactory.js.map +1 -0
- package/dist/client/managers/ComposedRequestManager.d.ts +82 -0
- package/dist/client/managers/ComposedRequestManager.d.ts.map +1 -0
- package/dist/client/managers/ComposedRequestManager.js +260 -0
- package/dist/client/managers/ComposedRequestManager.js.map +1 -0
- package/dist/client/managers/JWTAuthImplementation.d.ts +86 -0
- package/dist/client/managers/JWTAuthImplementation.d.ts.map +1 -0
- package/dist/client/managers/JWTAuthImplementation.js +240 -0
- package/dist/client/managers/JWTAuthImplementation.js.map +1 -0
- package/dist/client/managers/ManagersIndex.d.ts +6 -0
- package/dist/client/managers/ManagersIndex.d.ts.map +1 -0
- package/dist/client/managers/ManagersIndex.js +6 -0
- package/dist/client/managers/ManagersIndex.js.map +1 -0
- package/dist/client/managers/RequestManager.d.ts.map +1 -1
- package/dist/client/managers/RequestManager.js +5 -3
- package/dist/client/managers/RequestManager.js.map +1 -1
- package/dist/client/managers/composed/MigrationAdapter.d.ts +80 -0
- package/dist/client/managers/composed/MigrationAdapter.d.ts.map +1 -0
- package/dist/client/managers/composed/MigrationAdapter.js +214 -0
- package/dist/client/managers/composed/MigrationAdapter.js.map +1 -0
- package/dist/client/managers/composed/index.d.ts +23 -0
- package/dist/client/managers/composed/index.d.ts.map +1 -0
- package/dist/client/managers/composed/index.js +26 -0
- package/dist/client/managers/composed/index.js.map +1 -0
- package/dist/client/managers/implementations/ConfigurationProviderImpl.d.ts +27 -0
- package/dist/client/managers/implementations/ConfigurationProviderImpl.d.ts.map +1 -0
- package/dist/client/managers/implementations/ConfigurationProviderImpl.js +41 -0
- package/dist/client/managers/implementations/ConfigurationProviderImpl.js.map +1 -0
- package/dist/client/managers/implementations/ErrorHandlerImpl.d.ts +31 -0
- package/dist/client/managers/implementations/ErrorHandlerImpl.d.ts.map +1 -0
- package/dist/client/managers/implementations/ErrorHandlerImpl.js +73 -0
- package/dist/client/managers/implementations/ErrorHandlerImpl.js.map +1 -0
- package/dist/client/managers/implementations/ParameterValidatorImpl.d.ts +47 -0
- package/dist/client/managers/implementations/ParameterValidatorImpl.d.ts.map +1 -0
- package/dist/client/managers/implementations/ParameterValidatorImpl.js +141 -0
- package/dist/client/managers/implementations/ParameterValidatorImpl.js.map +1 -0
- package/dist/client/managers/interfaces/ManagerInterfaces.d.ts +147 -0
- package/dist/client/managers/interfaces/ManagerInterfaces.d.ts.map +1 -0
- package/dist/client/managers/interfaces/ManagerInterfaces.js +6 -0
- package/dist/client/managers/interfaces/ManagerInterfaces.js.map +1 -0
- package/dist/config/Config.d.ts +30 -0
- package/dist/config/Config.d.ts.map +1 -1
- package/dist/config/Config.js +30 -0
- package/dist/config/Config.js.map +1 -1
- package/dist/config/ConfigurationSchema.d.ts +75 -198
- package/dist/config/ConfigurationSchema.d.ts.map +1 -1
- package/dist/config/ConfigurationSchema.js +17 -17
- package/dist/config/ConfigurationSchema.js.map +1 -1
- package/dist/config/ServerConfiguration.d.ts +2 -2
- package/dist/config/ServerConfiguration.d.ts.map +1 -1
- package/dist/config/ServerConfiguration.js +15 -13
- package/dist/config/ServerConfiguration.js.map +1 -1
- package/dist/config/index.d.ts +8 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +11 -0
- package/dist/config/index.js.map +1 -0
- package/dist/docs/DocumentationGenerator.js +2 -2
- package/dist/docs/DocumentationGenerator.js.map +1 -1
- package/dist/dxt-entry.js +3 -3
- package/dist/dxt-entry.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -37
- package/dist/index.js.map +1 -1
- package/dist/performance/MetricsCollector.d.ts.map +1 -1
- package/dist/performance/MetricsCollector.js +5 -4
- package/dist/performance/MetricsCollector.js.map +1 -1
- package/dist/security/AISecurityScanner.js +7 -7
- package/dist/security/AISecurityScanner.js.map +1 -1
- package/dist/security/AutomatedRemediation.d.ts.map +1 -1
- package/dist/security/AutomatedRemediation.js +11 -11
- package/dist/security/AutomatedRemediation.js.map +1 -1
- package/dist/security/InputValidator.d.ts +50 -126
- package/dist/security/InputValidator.d.ts.map +1 -1
- package/dist/security/InputValidator.js +9 -9
- package/dist/security/InputValidator.js.map +1 -1
- package/dist/security/SecurityCIPipeline.d.ts +47 -5
- package/dist/security/SecurityCIPipeline.d.ts.map +1 -1
- package/dist/security/SecurityCIPipeline.js +390 -49
- package/dist/security/SecurityCIPipeline.js.map +1 -1
- package/dist/security/SecurityConfigManager.js +10 -10
- package/dist/security/SecurityConfigManager.js.map +1 -1
- package/dist/security/SecurityMonitoring.js +4 -4
- package/dist/security/SecurityMonitoring.js.map +1 -1
- package/dist/security/SecurityReviewer.d.ts.map +1 -1
- package/dist/security/SecurityReviewer.js +13 -6
- package/dist/security/SecurityReviewer.js.map +1 -1
- package/dist/security/index.js +3 -3
- package/dist/security/index.js.map +1 -1
- package/dist/server/ConnectionTester.js +5 -5
- package/dist/server/ConnectionTester.js.map +1 -1
- package/dist/server/ToolRegistry.d.ts +2 -2
- package/dist/server/ToolRegistry.d.ts.map +1 -1
- package/dist/server/ToolRegistry.js +7 -6
- package/dist/server/ToolRegistry.js.map +1 -1
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +9 -0
- package/dist/server/index.js.map +1 -0
- package/dist/tools/BaseToolManager.d.ts.map +1 -1
- package/dist/tools/BaseToolManager.js +11 -11
- package/dist/tools/BaseToolManager.js.map +1 -1
- package/dist/tools/auth.d.ts.map +1 -1
- package/dist/tools/auth.js +7 -7
- package/dist/tools/auth.js.map +1 -1
- package/dist/tools/comments.d.ts.map +1 -1
- package/dist/tools/comments.js +14 -14
- package/dist/tools/comments.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/media.d.ts.map +1 -1
- package/dist/tools/media.js +14 -11
- package/dist/tools/media.js.map +1 -1
- package/dist/tools/pages.d.ts.map +1 -1
- package/dist/tools/pages.js +12 -12
- package/dist/tools/pages.js.map +1 -1
- package/dist/tools/performance.d.ts.map +1 -1
- package/dist/tools/performance.js +13 -11
- package/dist/tools/performance.js.map +1 -1
- package/dist/tools/posts/PostHandlers.d.ts.map +1 -1
- package/dist/tools/posts/PostHandlers.js +16 -16
- package/dist/tools/posts/PostHandlers.js.map +1 -1
- package/dist/tools/posts/PostToolDefinitions.d.ts.map +1 -1
- package/dist/tools/posts/index.d.ts.map +1 -1
- package/dist/tools/seo/BulkOperations.d.ts +113 -0
- package/dist/tools/seo/BulkOperations.d.ts.map +1 -0
- package/dist/tools/seo/BulkOperations.js +398 -0
- package/dist/tools/seo/BulkOperations.js.map +1 -0
- package/dist/tools/seo/SEOHandlers.d.ts +55 -0
- package/dist/tools/seo/SEOHandlers.d.ts.map +1 -0
- package/dist/tools/seo/SEOHandlers.js +255 -0
- package/dist/tools/seo/SEOHandlers.js.map +1 -0
- package/dist/tools/seo/SEOToolDefinitions.d.ts +59 -0
- package/dist/tools/seo/SEOToolDefinitions.d.ts.map +1 -0
- package/dist/tools/seo/SEOToolDefinitions.js +385 -0
- package/dist/tools/seo/SEOToolDefinitions.js.map +1 -0
- package/dist/tools/seo/SEOTools.d.ts +203 -0
- package/dist/tools/seo/SEOTools.d.ts.map +1 -0
- package/dist/tools/seo/SEOTools.js +708 -0
- package/dist/tools/seo/SEOTools.js.map +1 -0
- package/dist/tools/seo/analyzers/ContentAnalyzer.d.ts +94 -0
- package/dist/tools/seo/analyzers/ContentAnalyzer.d.ts.map +1 -0
- package/dist/tools/seo/analyzers/ContentAnalyzer.js +402 -0
- package/dist/tools/seo/analyzers/ContentAnalyzer.js.map +1 -0
- package/dist/tools/seo/auditors/SiteAuditor.d.ts +121 -0
- package/dist/tools/seo/auditors/SiteAuditor.d.ts.map +1 -0
- package/dist/tools/seo/auditors/SiteAuditor.js +600 -0
- package/dist/tools/seo/auditors/SiteAuditor.js.map +1 -0
- package/dist/tools/seo/generators/MetaGenerator.d.ts +128 -0
- package/dist/tools/seo/generators/MetaGenerator.d.ts.map +1 -0
- package/dist/tools/seo/generators/MetaGenerator.js +547 -0
- package/dist/tools/seo/generators/MetaGenerator.js.map +1 -0
- package/dist/tools/seo/generators/SchemaGenerator.d.ts +204 -0
- package/dist/tools/seo/generators/SchemaGenerator.d.ts.map +1 -0
- package/dist/tools/seo/generators/SchemaGenerator.js +670 -0
- package/dist/tools/seo/generators/SchemaGenerator.js.map +1 -0
- package/dist/tools/seo/index.d.ts +17 -0
- package/dist/tools/seo/index.d.ts.map +1 -0
- package/dist/tools/seo/index.js +18 -0
- package/dist/tools/seo/index.js.map +1 -0
- package/dist/tools/seo/optimizers/InternalLinkingSuggester.d.ts +186 -0
- package/dist/tools/seo/optimizers/InternalLinkingSuggester.d.ts.map +1 -0
- package/dist/tools/seo/optimizers/InternalLinkingSuggester.js +683 -0
- package/dist/tools/seo/optimizers/InternalLinkingSuggester.js.map +1 -0
- package/dist/tools/site.d.ts.map +1 -1
- package/dist/tools/site.js +12 -12
- package/dist/tools/site.js.map +1 -1
- package/dist/tools/taxonomies.d.ts.map +1 -1
- package/dist/tools/taxonomies.js +20 -20
- package/dist/tools/taxonomies.js.map +1 -1
- package/dist/tools/users.d.ts.map +1 -1
- package/dist/tools/users.js +12 -12
- package/dist/tools/users.js.map +1 -1
- package/dist/types/client.d.ts +8 -6
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/client.js.map +1 -1
- package/dist/types/seo.d.ts +473 -0
- package/dist/types/seo.d.ts.map +1 -0
- package/dist/types/seo.js +94 -0
- package/dist/types/seo.js.map +1 -0
- package/dist/utils/enhancedError.js +1 -1
- package/dist/utils/enhancedError.js.map +1 -1
- package/dist/utils/error.d.ts.map +1 -1
- package/dist/utils/error.js +0 -1
- package/dist/utils/error.js.map +1 -1
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.js +3 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/toolWrapper.d.ts +2 -2
- package/dist/utils/toolWrapper.js +8 -8
- package/dist/utils/toolWrapper.js.map +1 -1
- package/dist/utils/validation/core.d.ts.map +1 -1
- package/dist/utils/validation/core.js.map +1 -1
- package/dist/utils/validation/index.d.ts.map +1 -1
- package/dist/utils/validation/index.js.map +1 -1
- package/dist/utils/validation/network.js +3 -3
- package/dist/utils/validation/network.js.map +1 -1
- package/dist/utils/validation/rateLimit.js.map +1 -1
- package/dist/utils/validation/security.js.map +1 -1
- package/dist/utils/validation/wordpress.js.map +1 -1
- package/dist/utils/version.d.ts +144 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +318 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +24 -58
- package/src/cache/CacheInvalidation.ts +183 -20
- package/src/cache/HttpCacheWrapper.ts +8 -5
- package/src/cache/SEOCacheManager.ts +330 -0
- package/src/cache/__tests__/CacheInvalidation.test.ts +6 -11
- package/src/cache/__tests__/CachedWordPressClient.test.ts +37 -62
- package/src/client/SEOWordPressClient.ts +876 -0
- package/src/client/api.ts +50 -21
- package/src/client/auth.ts +19 -19
- package/src/client/index.ts +16 -0
- package/src/client/managers/AuthManager.ts +175 -0
- package/src/client/managers/AuthenticationManager.ts +16 -14
- package/src/client/managers/BaseManager.ts +24 -5
- package/src/client/managers/ComposedAuthenticationManager.ts +409 -0
- package/src/client/managers/ComposedManagerFactory.ts +231 -0
- package/src/client/managers/ComposedRequestManager.ts +336 -0
- package/src/client/managers/JWTAuthImplementation.ts +326 -0
- package/src/client/managers/ManagersIndex.ts +6 -0
- package/src/client/managers/RequestManager.ts +9 -7
- package/src/client/managers/composed/MigrationAdapter.ts +263 -0
- package/src/client/managers/composed/index.ts +47 -0
- package/src/client/managers/implementations/ConfigurationProviderImpl.ts +52 -0
- package/src/client/managers/implementations/ErrorHandlerImpl.ts +102 -0
- package/src/client/managers/implementations/ParameterValidatorImpl.ts +221 -0
- package/src/client/managers/interfaces/ManagerInterfaces.ts +171 -0
- package/src/config/Config.ts +63 -0
- package/src/config/ConfigurationSchema.ts +17 -17
- package/src/config/ServerConfiguration.ts +18 -16
- package/src/config/index.ts +13 -0
- package/src/docs/DocumentationGenerator.ts +2 -2
- package/src/dxt-entry.ts +3 -3
- package/src/index.ts +43 -43
- package/src/performance/MetricsCollector.ts +15 -11
- package/src/security/AISecurityScanner.ts +7 -7
- package/src/security/AutomatedRemediation.ts +13 -11
- package/src/security/InputValidator.ts +10 -9
- package/src/security/SecurityCIPipeline.ts +494 -56
- package/src/security/SecurityConfigManager.ts +10 -10
- package/src/security/SecurityMonitoring.ts +5 -5
- package/src/security/SecurityReviewer.ts +13 -6
- package/src/security/index.ts +3 -3
- package/src/server/ConnectionTester.ts +5 -5
- package/src/server/ToolRegistry.ts +9 -8
- package/src/server/index.ts +10 -0
- package/src/tools/BaseToolManager.ts +55 -83
- package/src/tools/auth.ts +21 -12
- package/src/tools/comments.ts +23 -19
- package/src/tools/index.ts +1 -0
- package/src/tools/media.ts +23 -20
- package/src/tools/pages.ts +20 -13
- package/src/tools/performance.ts +101 -32
- package/src/tools/posts/PostHandlers.ts +23 -23
- package/src/tools/posts/PostToolDefinitions.ts +1 -1
- package/src/tools/posts/index.ts +2 -2
- package/src/tools/seo/BulkOperations.ts +557 -0
- package/src/tools/seo/SEOHandlers.ts +296 -0
- package/src/tools/seo/SEOToolDefinitions.ts +402 -0
- package/src/tools/seo/SEOTools.ts +871 -0
- package/src/tools/seo/analyzers/ContentAnalyzer.ts +493 -0
- package/src/tools/seo/auditors/SiteAuditor.ts +787 -0
- package/src/tools/seo/generators/MetaGenerator.ts +694 -0
- package/src/tools/seo/generators/SchemaGenerator.ts +955 -0
- package/src/tools/seo/index.ts +47 -0
- package/src/tools/seo/optimizers/InternalLinkingSuggester.ts +934 -0
- package/src/tools/site.ts +27 -26
- package/src/tools/taxonomies.ts +29 -25
- package/src/tools/users.ts +20 -13
- package/src/types/client.ts +8 -6
- package/src/types/seo.ts +546 -0
- package/src/utils/enhancedError.ts +1 -1
- package/src/utils/error.ts +1 -2
- package/src/utils/index.ts +23 -0
- package/src/utils/logger.ts +3 -3
- package/src/utils/toolWrapper.ts +10 -10
- package/src/utils/validation/core.ts +2 -2
- package/src/utils/validation/index.ts +2 -2
- package/src/utils/validation/network.ts +5 -5
- package/src/utils/validation/rateLimit.ts +1 -1
- package/src/utils/validation/security.ts +1 -1
- package/src/utils/validation/wordpress.ts +1 -1
- package/src/utils/version.ts +402 -0
- package/src/dxt-entry.cjs +0 -68
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Site Auditor
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive SEO audit system for WordPress sites that analyzes technical SEO,
|
|
5
|
+
* content quality, site architecture, performance metrics, and accessibility.
|
|
6
|
+
* Provides actionable recommendations with priority scoring to guide optimization efforts.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Technical SEO analysis (meta tags, structured data, crawlability)
|
|
10
|
+
* - Content quality assessment (readability, keyword optimization, freshness)
|
|
11
|
+
* - Site architecture evaluation (internal linking, navigation, URL structure)
|
|
12
|
+
* - Performance monitoring (page speed, Core Web Vitals, mobile optimization)
|
|
13
|
+
* - Accessibility compliance checking (WCAG guidelines)
|
|
14
|
+
* - Competitive analysis and benchmarking
|
|
15
|
+
* - Automated issue detection with severity scoring
|
|
16
|
+
* - Detailed reporting with actionable recommendations
|
|
17
|
+
*
|
|
18
|
+
* @since 2.7.0
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { WordPressClient } from "../../../client/api.js";
|
|
22
|
+
import { LoggerFactory } from "../../../utils/logger.js";
|
|
23
|
+
import type { SiteAuditResult, SEOToolParams, AuditIssue, AuditSection } from "../../../types/seo.js";
|
|
24
|
+
import type { WordPressPost, WordPressPage } from "../../../types/wordpress.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Configuration for site audit behavior
|
|
28
|
+
*/
|
|
29
|
+
interface SiteAuditConfig {
|
|
30
|
+
/** Include technical SEO analysis */
|
|
31
|
+
includeTechnical: boolean;
|
|
32
|
+
|
|
33
|
+
/** Include content quality analysis */
|
|
34
|
+
includeContent: boolean;
|
|
35
|
+
|
|
36
|
+
/** Include site architecture analysis */
|
|
37
|
+
includeArchitecture: boolean;
|
|
38
|
+
|
|
39
|
+
/** Include performance analysis */
|
|
40
|
+
includePerformance: boolean;
|
|
41
|
+
|
|
42
|
+
/** Include accessibility analysis */
|
|
43
|
+
includeAccessibility: boolean;
|
|
44
|
+
|
|
45
|
+
/** Maximum pages to analyze for detailed content audit */
|
|
46
|
+
maxPagesForContentAudit: number;
|
|
47
|
+
|
|
48
|
+
/** Minimum severity level to include in results */
|
|
49
|
+
minSeverityLevel: "low" | "medium" | "high" | "critical";
|
|
50
|
+
|
|
51
|
+
/** Include competitor analysis */
|
|
52
|
+
includeCompetitorAnalysis: boolean;
|
|
53
|
+
|
|
54
|
+
/** Competitor URLs for analysis */
|
|
55
|
+
competitorUrls: string[];
|
|
56
|
+
|
|
57
|
+
/** Generate detailed recommendations */
|
|
58
|
+
includeRecommendations: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Site audit issue severity levels
|
|
63
|
+
*/
|
|
64
|
+
const IssueSeverity = {
|
|
65
|
+
LOW: "low" as const,
|
|
66
|
+
MEDIUM: "medium" as const,
|
|
67
|
+
HIGH: "high" as const,
|
|
68
|
+
CRITICAL: "critical" as const,
|
|
69
|
+
} as const;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Site audit categories
|
|
73
|
+
*/
|
|
74
|
+
const AuditCategory = {
|
|
75
|
+
TECHNICAL: "technical" as const,
|
|
76
|
+
CONTENT: "content" as const,
|
|
77
|
+
ARCHITECTURE: "architecture" as const,
|
|
78
|
+
PERFORMANCE: "performance" as const,
|
|
79
|
+
ACCESSIBILITY: "accessibility" as const,
|
|
80
|
+
} as const;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Comprehensive WordPress site SEO auditor
|
|
84
|
+
*/
|
|
85
|
+
export class SiteAuditor {
|
|
86
|
+
private logger = LoggerFactory.tool("site_auditor");
|
|
87
|
+
private config: SiteAuditConfig;
|
|
88
|
+
|
|
89
|
+
constructor(
|
|
90
|
+
private client: WordPressClient,
|
|
91
|
+
config: Partial<SiteAuditConfig> = {},
|
|
92
|
+
) {
|
|
93
|
+
this.config = {
|
|
94
|
+
includeTechnical: true,
|
|
95
|
+
includeContent: true,
|
|
96
|
+
includeArchitecture: true,
|
|
97
|
+
includePerformance: true,
|
|
98
|
+
includeAccessibility: false, // Requires external tools
|
|
99
|
+
maxPagesForContentAudit: 50,
|
|
100
|
+
minSeverityLevel: "medium",
|
|
101
|
+
includeCompetitorAnalysis: false,
|
|
102
|
+
competitorUrls: [],
|
|
103
|
+
includeRecommendations: true,
|
|
104
|
+
...config,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get current audit configuration
|
|
110
|
+
*/
|
|
111
|
+
getConfig(): SiteAuditConfig {
|
|
112
|
+
return { ...this.config };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Update audit configuration
|
|
117
|
+
*/
|
|
118
|
+
updateConfig(updates: Partial<SiteAuditConfig>): void {
|
|
119
|
+
this.config = { ...this.config, ...updates };
|
|
120
|
+
this.logger.debug("Site audit configuration updated", updates);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Perform comprehensive site audit
|
|
125
|
+
*/
|
|
126
|
+
async performSiteAudit(params: SEOToolParams): Promise<SiteAuditResult> {
|
|
127
|
+
this.logger.info("Starting comprehensive site audit", {
|
|
128
|
+
site: params.site,
|
|
129
|
+
auditScope: {
|
|
130
|
+
technical: this.config.includeTechnical,
|
|
131
|
+
content: this.config.includeContent,
|
|
132
|
+
architecture: this.config.includeArchitecture,
|
|
133
|
+
performance: this.config.includePerformance,
|
|
134
|
+
accessibility: this.config.includeAccessibility,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const startTime = Date.now();
|
|
139
|
+
const issues: AuditIssue[] = [];
|
|
140
|
+
const sections: AuditSection[] = [];
|
|
141
|
+
const recommendations: string[] = [];
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
// Get site data
|
|
145
|
+
const siteData = await this.collectSiteData(params);
|
|
146
|
+
|
|
147
|
+
// Technical SEO audit
|
|
148
|
+
if (this.config.includeTechnical) {
|
|
149
|
+
const technicalResults = await this.auditTechnicalSEO(siteData, params);
|
|
150
|
+
sections.push(technicalResults.section);
|
|
151
|
+
issues.push(...technicalResults.issues);
|
|
152
|
+
if (this.config.includeRecommendations) {
|
|
153
|
+
recommendations.push(...technicalResults.recommendations);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Content quality audit
|
|
158
|
+
if (this.config.includeContent) {
|
|
159
|
+
const contentResults = await this.auditContentQuality(siteData, params);
|
|
160
|
+
sections.push(contentResults.section);
|
|
161
|
+
issues.push(...contentResults.issues);
|
|
162
|
+
if (this.config.includeRecommendations) {
|
|
163
|
+
recommendations.push(...contentResults.recommendations);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Site architecture audit
|
|
168
|
+
if (this.config.includeArchitecture) {
|
|
169
|
+
const architectureResults = await this.auditSiteArchitecture(siteData, params);
|
|
170
|
+
sections.push(architectureResults.section);
|
|
171
|
+
issues.push(...architectureResults.issues);
|
|
172
|
+
if (this.config.includeRecommendations) {
|
|
173
|
+
recommendations.push(...architectureResults.recommendations);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Performance audit
|
|
178
|
+
if (this.config.includePerformance) {
|
|
179
|
+
const performanceResults = await this.auditPerformance(siteData, params);
|
|
180
|
+
sections.push(performanceResults.section);
|
|
181
|
+
issues.push(...performanceResults.issues);
|
|
182
|
+
if (this.config.includeRecommendations) {
|
|
183
|
+
recommendations.push(...performanceResults.recommendations);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Accessibility audit
|
|
188
|
+
if (this.config.includeAccessibility) {
|
|
189
|
+
const accessibilityResults = await this.auditAccessibility(siteData, params);
|
|
190
|
+
sections.push(accessibilityResults.section);
|
|
191
|
+
issues.push(...accessibilityResults.issues);
|
|
192
|
+
if (this.config.includeRecommendations) {
|
|
193
|
+
recommendations.push(...accessibilityResults.recommendations);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Filter issues by severity
|
|
198
|
+
const filteredIssues = this.filterIssuesBySeverity(issues);
|
|
199
|
+
|
|
200
|
+
// Calculate overall score
|
|
201
|
+
const overallScore = this.calculateOverallScore(sections, filteredIssues);
|
|
202
|
+
|
|
203
|
+
const auditResult: SiteAuditResult = {
|
|
204
|
+
timestamp: new Date().toISOString(),
|
|
205
|
+
siteUrl: siteData.siteUrl,
|
|
206
|
+
overallScore,
|
|
207
|
+
sections,
|
|
208
|
+
issues: filteredIssues,
|
|
209
|
+
recommendations: this.config.includeRecommendations ? recommendations : [],
|
|
210
|
+
summary: this.generateAuditSummary(sections, filteredIssues),
|
|
211
|
+
processingTime: Date.now() - startTime,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
this.logger.info("Site audit completed", {
|
|
215
|
+
overallScore,
|
|
216
|
+
totalIssues: filteredIssues.length,
|
|
217
|
+
criticalIssues: filteredIssues.filter((i) => i.severity === IssueSeverity.CRITICAL).length,
|
|
218
|
+
processingTime: auditResult.processingTime,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return auditResult;
|
|
222
|
+
} catch (_error) {
|
|
223
|
+
this.logger.error("Site audit failed", {
|
|
224
|
+
_error: _error instanceof Error ? _error.message : String(_error),
|
|
225
|
+
});
|
|
226
|
+
throw _error;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Collect comprehensive site data for analysis
|
|
232
|
+
*/
|
|
233
|
+
private async collectSiteData(params: SEOToolParams) {
|
|
234
|
+
this.logger.debug("Collecting site data for audit");
|
|
235
|
+
|
|
236
|
+
// Get all posts and pages
|
|
237
|
+
const posts = await this.client.getPosts({ per_page: this.config.maxPagesForContentAudit, status: ["publish"] });
|
|
238
|
+
const pages = await this.client.getPages({ per_page: this.config.maxPagesForContentAudit, status: ["publish"] });
|
|
239
|
+
|
|
240
|
+
// Get site info (mock for now)
|
|
241
|
+
const siteUrl = "https://example.com"; // Would come from WordPress REST API
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
siteUrl,
|
|
245
|
+
posts: posts as WordPressPost[],
|
|
246
|
+
pages: pages as WordPressPage[],
|
|
247
|
+
totalContent: (posts?.length || 0) + (pages?.length || 0),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Audit technical SEO aspects
|
|
253
|
+
*/
|
|
254
|
+
private async auditTechnicalSEO(
|
|
255
|
+
siteData: { siteUrl: string; posts: WordPressPost[]; pages: WordPressPage[]; totalContent: number },
|
|
256
|
+
params: SEOToolParams,
|
|
257
|
+
) {
|
|
258
|
+
this.logger.debug("Auditing technical SEO");
|
|
259
|
+
|
|
260
|
+
const issues: AuditIssue[] = [];
|
|
261
|
+
const recommendations: string[] = [];
|
|
262
|
+
let score = 100;
|
|
263
|
+
|
|
264
|
+
// Check for missing meta descriptions
|
|
265
|
+
const itemsWithoutMeta = [...siteData.posts, ...siteData.pages].filter(
|
|
266
|
+
(item) => !item.excerpt?.rendered || item.excerpt.rendered.length < 120,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
if (itemsWithoutMeta.length > 0) {
|
|
270
|
+
const severity = itemsWithoutMeta.length > 10 ? IssueSeverity.HIGH : IssueSeverity.MEDIUM;
|
|
271
|
+
score -= itemsWithoutMeta.length > 10 ? 15 : 8;
|
|
272
|
+
|
|
273
|
+
issues.push({
|
|
274
|
+
id: "missing-meta-descriptions",
|
|
275
|
+
title: "Missing or Short Meta Descriptions",
|
|
276
|
+
description: `${itemsWithoutMeta.length} pages lack proper meta descriptions (minimum 120 characters)`,
|
|
277
|
+
severity,
|
|
278
|
+
category: AuditCategory.TECHNICAL,
|
|
279
|
+
affectedItems: itemsWithoutMeta.map((item) => item.title?.rendered || "Untitled"),
|
|
280
|
+
impact: "Meta descriptions affect click-through rates from search results",
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
recommendations.push(
|
|
284
|
+
`Add compelling meta descriptions (150-160 characters) to ${itemsWithoutMeta.length} pages to improve search result click-through rates`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Check for duplicate titles
|
|
289
|
+
const titleMap = new Map<string, number>();
|
|
290
|
+
[...siteData.posts, ...siteData.pages].forEach((item) => {
|
|
291
|
+
const title = item.title?.rendered?.toLowerCase() || "";
|
|
292
|
+
titleMap.set(title, (titleMap.get(title) || 0) + 1);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const duplicateTitles = Array.from(titleMap.entries()).filter(([_, count]) => count > 1);
|
|
296
|
+
if (duplicateTitles.length > 0) {
|
|
297
|
+
score -= duplicateTitles.length * 5;
|
|
298
|
+
|
|
299
|
+
issues.push({
|
|
300
|
+
id: "duplicate-titles",
|
|
301
|
+
title: "Duplicate Page Titles",
|
|
302
|
+
description: `${duplicateTitles.length} titles are used on multiple pages`,
|
|
303
|
+
severity: IssueSeverity.HIGH,
|
|
304
|
+
category: AuditCategory.TECHNICAL,
|
|
305
|
+
affectedItems: duplicateTitles.map(([title]) => title),
|
|
306
|
+
impact: "Duplicate titles confuse search engines and reduce page rankings",
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
recommendations.push(
|
|
310
|
+
`Create unique, descriptive titles for all pages to improve search engine understanding and rankings`,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Check for very short content
|
|
315
|
+
const shortContent = [...siteData.posts, ...siteData.pages].filter((item) => {
|
|
316
|
+
const content = this.extractTextContent(item.content?.rendered || "");
|
|
317
|
+
return this.countWords(content) < 300;
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
if (shortContent.length > 0) {
|
|
321
|
+
score -= Math.min(shortContent.length * 3, 20);
|
|
322
|
+
|
|
323
|
+
issues.push({
|
|
324
|
+
id: "thin-content",
|
|
325
|
+
title: "Thin Content Pages",
|
|
326
|
+
description: `${shortContent.length} pages have less than 300 words of content`,
|
|
327
|
+
severity: IssueSeverity.MEDIUM,
|
|
328
|
+
category: AuditCategory.TECHNICAL,
|
|
329
|
+
affectedItems: shortContent.map((item) => item.title?.rendered || "Untitled"),
|
|
330
|
+
impact: "Thin content may not rank well in search results",
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
recommendations.push(
|
|
334
|
+
`Expand content on ${shortContent.length} pages to at least 300 words for better search rankings`,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
section: {
|
|
340
|
+
name: "Technical SEO",
|
|
341
|
+
score: Math.max(0, score),
|
|
342
|
+
issues: issues.length,
|
|
343
|
+
passed: issues.length === 0,
|
|
344
|
+
},
|
|
345
|
+
issues,
|
|
346
|
+
recommendations,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Audit content quality
|
|
352
|
+
*/
|
|
353
|
+
private async auditContentQuality(
|
|
354
|
+
siteData: { siteUrl: string; posts: WordPressPost[]; pages: WordPressPage[]; totalContent: number },
|
|
355
|
+
params: SEOToolParams,
|
|
356
|
+
) {
|
|
357
|
+
this.logger.debug("Auditing content quality");
|
|
358
|
+
|
|
359
|
+
const issues: AuditIssue[] = [];
|
|
360
|
+
const recommendations: string[] = [];
|
|
361
|
+
let score = 100;
|
|
362
|
+
|
|
363
|
+
// Check content freshness
|
|
364
|
+
const oldContent = [...siteData.posts, ...siteData.pages].filter((item) => {
|
|
365
|
+
const lastModified = new Date(item.modified || item.date || 0);
|
|
366
|
+
const daysSinceModified = (Date.now() - lastModified.getTime()) / (1000 * 60 * 60 * 24);
|
|
367
|
+
return daysSinceModified > 365; // More than 1 year old
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
if (oldContent.length > 0) {
|
|
371
|
+
const ratio = oldContent.length / siteData.totalContent;
|
|
372
|
+
score -= Math.min(ratio * 30, 25);
|
|
373
|
+
|
|
374
|
+
issues.push({
|
|
375
|
+
id: "outdated-content",
|
|
376
|
+
title: "Outdated Content",
|
|
377
|
+
description: `${oldContent.length} pages (${(ratio * 100).toFixed(1)}%) haven't been updated in over a year`,
|
|
378
|
+
severity: ratio > 0.5 ? IssueSeverity.HIGH : IssueSeverity.MEDIUM,
|
|
379
|
+
category: AuditCategory.CONTENT,
|
|
380
|
+
affectedItems: oldContent.slice(0, 10).map((item) => item.title?.rendered || "Untitled"),
|
|
381
|
+
impact: "Outdated content may lose search rankings and user engagement",
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
recommendations.push(
|
|
385
|
+
`Review and update ${oldContent.length} outdated pages to maintain content freshness and search rankings`,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Check for missing featured images
|
|
390
|
+
const noFeaturedImage = [...siteData.posts, ...siteData.pages].filter(
|
|
391
|
+
(item) => !item.featured_media || item.featured_media === 0,
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
if (noFeaturedImage.length > 0) {
|
|
395
|
+
score -= Math.min(noFeaturedImage.length * 2, 15);
|
|
396
|
+
|
|
397
|
+
issues.push({
|
|
398
|
+
id: "missing-featured-images",
|
|
399
|
+
title: "Missing Featured Images",
|
|
400
|
+
description: `${noFeaturedImage.length} posts/pages lack featured images`,
|
|
401
|
+
severity: IssueSeverity.LOW,
|
|
402
|
+
category: AuditCategory.CONTENT,
|
|
403
|
+
affectedItems: noFeaturedImage.slice(0, 10).map((item) => item.title?.rendered || "Untitled"),
|
|
404
|
+
impact: "Featured images improve social sharing and visual appeal",
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
recommendations.push(
|
|
408
|
+
`Add featured images to ${noFeaturedImage.length} posts/pages to improve social sharing and engagement`,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Analyze readability (simplified scoring)
|
|
413
|
+
const hardToReadContent = [...siteData.posts, ...siteData.pages].filter((item) => {
|
|
414
|
+
const content = this.extractTextContent(item.content?.rendered || "");
|
|
415
|
+
const readabilityScore = this.calculateReadabilityScore(content);
|
|
416
|
+
return readabilityScore < 60; // Below average readability
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
if (hardToReadContent.length > 0) {
|
|
420
|
+
score -= Math.min(hardToReadContent.length * 3, 20);
|
|
421
|
+
|
|
422
|
+
issues.push({
|
|
423
|
+
id: "poor-readability",
|
|
424
|
+
title: "Poor Content Readability",
|
|
425
|
+
description: `${hardToReadContent.length} pages have below-average readability scores`,
|
|
426
|
+
severity: IssueSeverity.MEDIUM,
|
|
427
|
+
category: AuditCategory.CONTENT,
|
|
428
|
+
affectedItems: hardToReadContent.slice(0, 10).map((item) => item.title?.rendered || "Untitled"),
|
|
429
|
+
impact: "Poor readability reduces user engagement and time on page",
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
recommendations.push(
|
|
433
|
+
`Improve readability on ${hardToReadContent.length} pages using shorter sentences, simpler words, and better formatting`,
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
section: {
|
|
439
|
+
name: "Content Quality",
|
|
440
|
+
score: Math.max(0, score),
|
|
441
|
+
issues: issues.length,
|
|
442
|
+
passed: issues.length === 0,
|
|
443
|
+
},
|
|
444
|
+
issues,
|
|
445
|
+
recommendations,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Audit site architecture
|
|
451
|
+
*/
|
|
452
|
+
private async auditSiteArchitecture(
|
|
453
|
+
siteData: { siteUrl: string; posts: WordPressPost[]; pages: WordPressPage[]; totalContent: number },
|
|
454
|
+
params: SEOToolParams,
|
|
455
|
+
) {
|
|
456
|
+
this.logger.debug("Auditing site architecture");
|
|
457
|
+
|
|
458
|
+
const issues: AuditIssue[] = [];
|
|
459
|
+
const recommendations: string[] = [];
|
|
460
|
+
let score = 100;
|
|
461
|
+
|
|
462
|
+
// Check for orphaned content (simplified - no actual link analysis)
|
|
463
|
+
const totalPages = siteData.totalContent;
|
|
464
|
+
const orphanedEstimate = Math.max(0, Math.floor(totalPages * 0.1)); // Estimate 10% orphaned
|
|
465
|
+
|
|
466
|
+
if (orphanedEstimate > 0) {
|
|
467
|
+
score -= Math.min(orphanedEstimate * 2, 15);
|
|
468
|
+
|
|
469
|
+
issues.push({
|
|
470
|
+
id: "orphaned-content",
|
|
471
|
+
title: "Potentially Orphaned Content",
|
|
472
|
+
description: `Estimated ${orphanedEstimate} pages may lack sufficient internal links`,
|
|
473
|
+
severity: IssueSeverity.MEDIUM,
|
|
474
|
+
category: AuditCategory.ARCHITECTURE,
|
|
475
|
+
affectedItems: ["Analysis requires full crawl to identify specific pages"],
|
|
476
|
+
impact: "Orphaned content may not be discoverable by users or search engines",
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
recommendations.push(
|
|
480
|
+
`Perform internal linking audit to identify and connect orphaned content to main site architecture`,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Check URL structure (simplified analysis)
|
|
485
|
+
const longUrls = [...siteData.posts, ...siteData.pages].filter(
|
|
486
|
+
(item) => (item.link || item.slug || "").length > 100,
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
if (longUrls.length > 0) {
|
|
490
|
+
score -= Math.min(longUrls.length, 10);
|
|
491
|
+
|
|
492
|
+
issues.push({
|
|
493
|
+
id: "long-urls",
|
|
494
|
+
title: "Overly Long URLs",
|
|
495
|
+
description: `${longUrls.length} pages have URLs longer than 100 characters`,
|
|
496
|
+
severity: IssueSeverity.LOW,
|
|
497
|
+
category: AuditCategory.ARCHITECTURE,
|
|
498
|
+
affectedItems: longUrls.slice(0, 5).map((item) => item.title?.rendered || "Untitled"),
|
|
499
|
+
impact: "Long URLs are harder to share and may be truncated in search results",
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
recommendations.push(`Optimize ${longUrls.length} URLs to be shorter and more descriptive`);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Check for proper heading structure (basic analysis)
|
|
506
|
+
const poorHeadingStructure = [...siteData.posts, ...siteData.pages].filter((item) => {
|
|
507
|
+
const content = item.content?.rendered || "";
|
|
508
|
+
const hasH1 = /<h1/i.test(content);
|
|
509
|
+
const hasMultipleH1 = (content.match(/<h1/gi) || []).length > 1;
|
|
510
|
+
return !hasH1 || hasMultipleH1;
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
if (poorHeadingStructure.length > 0) {
|
|
514
|
+
score -= Math.min(poorHeadingStructure.length * 2, 15);
|
|
515
|
+
|
|
516
|
+
issues.push({
|
|
517
|
+
id: "heading-structure",
|
|
518
|
+
title: "Poor Heading Structure",
|
|
519
|
+
description: `${poorHeadingStructure.length} pages have missing or multiple H1 tags`,
|
|
520
|
+
severity: IssueSeverity.MEDIUM,
|
|
521
|
+
category: AuditCategory.ARCHITECTURE,
|
|
522
|
+
affectedItems: poorHeadingStructure.slice(0, 10).map((item) => item.title?.rendered || "Untitled"),
|
|
523
|
+
impact: "Poor heading structure affects content hierarchy and SEO",
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
recommendations.push(
|
|
527
|
+
`Fix heading structure on ${poorHeadingStructure.length} pages: use exactly one H1 and logical heading hierarchy`,
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
section: {
|
|
533
|
+
name: "Site Architecture",
|
|
534
|
+
score: Math.max(0, score),
|
|
535
|
+
issues: issues.length,
|
|
536
|
+
passed: issues.length === 0,
|
|
537
|
+
},
|
|
538
|
+
issues,
|
|
539
|
+
recommendations,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Audit performance aspects
|
|
545
|
+
*/
|
|
546
|
+
private async auditPerformance(
|
|
547
|
+
siteData: { siteUrl: string; posts: WordPressPost[]; pages: WordPressPage[]; totalContent: number },
|
|
548
|
+
params: SEOToolParams,
|
|
549
|
+
) {
|
|
550
|
+
this.logger.debug("Auditing performance");
|
|
551
|
+
|
|
552
|
+
const issues: AuditIssue[] = [];
|
|
553
|
+
const recommendations: string[] = [];
|
|
554
|
+
let score = 100;
|
|
555
|
+
|
|
556
|
+
// Check for large images (estimated based on content analysis)
|
|
557
|
+
const imagesInContent = [...siteData.posts, ...siteData.pages].reduce((count, item) => {
|
|
558
|
+
const content = item.content?.rendered || "";
|
|
559
|
+
const imageMatches = content.match(/<img[^>]*>/gi) || [];
|
|
560
|
+
return count + imageMatches.length;
|
|
561
|
+
}, 0);
|
|
562
|
+
|
|
563
|
+
if (imagesInContent > 20) {
|
|
564
|
+
score -= 10;
|
|
565
|
+
|
|
566
|
+
issues.push({
|
|
567
|
+
id: "image-optimization",
|
|
568
|
+
title: "Image Optimization Needed",
|
|
569
|
+
description: `${imagesInContent} images found - optimization may be needed`,
|
|
570
|
+
severity: IssueSeverity.MEDIUM,
|
|
571
|
+
category: AuditCategory.PERFORMANCE,
|
|
572
|
+
affectedItems: ["Requires detailed analysis of individual images"],
|
|
573
|
+
impact: "Unoptimized images slow page loading and affect user experience",
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
recommendations.push(
|
|
577
|
+
`Audit and optimize ${imagesInContent} images for web performance: compress, resize, and use modern formats`,
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Check for potential caching issues (heuristic)
|
|
582
|
+
const dynamicContentPages = siteData.posts.length;
|
|
583
|
+
if (dynamicContentPages > 50) {
|
|
584
|
+
score -= 15;
|
|
585
|
+
|
|
586
|
+
issues.push({
|
|
587
|
+
id: "caching-strategy",
|
|
588
|
+
title: "Caching Strategy Review Needed",
|
|
589
|
+
description: `${dynamicContentPages} dynamic pages may benefit from enhanced caching`,
|
|
590
|
+
severity: IssueSeverity.HIGH,
|
|
591
|
+
category: AuditCategory.PERFORMANCE,
|
|
592
|
+
affectedItems: ["All dynamic content pages"],
|
|
593
|
+
impact: "Inadequate caching increases server load and page response times",
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
recommendations.push(
|
|
597
|
+
`Implement comprehensive caching strategy for ${dynamicContentPages} pages to improve performance`,
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Check for external dependencies (basic analysis)
|
|
602
|
+
const externalDependencies = [...siteData.posts, ...siteData.pages].reduce((count, item) => {
|
|
603
|
+
const content = item.content?.rendered || "";
|
|
604
|
+
const externalLinks = content.match(/https?:\/\/(?!example\.com)[^"'\s>]*/gi) || [];
|
|
605
|
+
return count + externalLinks.length;
|
|
606
|
+
}, 0);
|
|
607
|
+
|
|
608
|
+
if (externalDependencies > 30) {
|
|
609
|
+
score -= 5;
|
|
610
|
+
|
|
611
|
+
issues.push({
|
|
612
|
+
id: "external-dependencies",
|
|
613
|
+
title: "Many External Dependencies",
|
|
614
|
+
description: `${externalDependencies} external resources may affect loading speed`,
|
|
615
|
+
severity: IssueSeverity.LOW,
|
|
616
|
+
category: AuditCategory.PERFORMANCE,
|
|
617
|
+
affectedItems: ["Various pages with external resources"],
|
|
618
|
+
impact: "External dependencies can slow page loading if third-party services are slow",
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
recommendations.push(
|
|
622
|
+
`Review ${externalDependencies} external dependencies and consider local alternatives where possible`,
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return {
|
|
627
|
+
section: {
|
|
628
|
+
name: "Performance",
|
|
629
|
+
score: Math.max(0, score),
|
|
630
|
+
issues: issues.length,
|
|
631
|
+
passed: issues.length === 0,
|
|
632
|
+
},
|
|
633
|
+
issues,
|
|
634
|
+
recommendations,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Audit accessibility (basic implementation)
|
|
640
|
+
*/
|
|
641
|
+
private async auditAccessibility(
|
|
642
|
+
siteData: { siteUrl: string; posts: WordPressPost[]; pages: WordPressPage[]; totalContent: number },
|
|
643
|
+
params: SEOToolParams,
|
|
644
|
+
) {
|
|
645
|
+
this.logger.debug("Auditing accessibility");
|
|
646
|
+
|
|
647
|
+
const issues: AuditIssue[] = [];
|
|
648
|
+
const recommendations: string[] = [];
|
|
649
|
+
let score = 100;
|
|
650
|
+
|
|
651
|
+
// Check for missing alt text on images
|
|
652
|
+
const imagesWithoutAlt = [...siteData.posts, ...siteData.pages].filter((item) => {
|
|
653
|
+
const content = item.content?.rendered || "";
|
|
654
|
+
const imgTags = content.match(/<img[^>]*>/gi) || [];
|
|
655
|
+
return imgTags.some((img: string) => !img.includes("alt=") || img.includes('alt=""'));
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
if (imagesWithoutAlt.length > 0) {
|
|
659
|
+
score -= Math.min(imagesWithoutAlt.length * 5, 25);
|
|
660
|
+
|
|
661
|
+
issues.push({
|
|
662
|
+
id: "missing-alt-text",
|
|
663
|
+
title: "Missing Image Alt Text",
|
|
664
|
+
description: `${imagesWithoutAlt.length} pages contain images without proper alt text`,
|
|
665
|
+
severity: IssueSeverity.HIGH,
|
|
666
|
+
category: AuditCategory.ACCESSIBILITY,
|
|
667
|
+
affectedItems: imagesWithoutAlt.slice(0, 10).map((item) => item.title?.rendered || "Untitled"),
|
|
668
|
+
impact: "Missing alt text prevents screen readers from describing images to visually impaired users",
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
recommendations.push(
|
|
672
|
+
`Add descriptive alt text to images on ${imagesWithoutAlt.length} pages for accessibility compliance`,
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
return {
|
|
677
|
+
section: {
|
|
678
|
+
name: "Accessibility",
|
|
679
|
+
score: Math.max(0, score),
|
|
680
|
+
issues: issues.length,
|
|
681
|
+
passed: issues.length === 0,
|
|
682
|
+
},
|
|
683
|
+
issues,
|
|
684
|
+
recommendations,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Filter issues by configured severity level
|
|
690
|
+
*/
|
|
691
|
+
private filterIssuesBySeverity(issues: AuditIssue[]): AuditIssue[] {
|
|
692
|
+
const severityOrder = {
|
|
693
|
+
low: 0,
|
|
694
|
+
medium: 1,
|
|
695
|
+
high: 2,
|
|
696
|
+
critical: 3,
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
const minLevel = severityOrder[this.config.minSeverityLevel];
|
|
700
|
+
|
|
701
|
+
return issues.filter((issue) => severityOrder[issue.severity] >= minLevel);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Calculate overall site score
|
|
706
|
+
*/
|
|
707
|
+
private calculateOverallScore(sections: AuditSection[], issues: AuditIssue[]): number {
|
|
708
|
+
if (sections.length === 0) return 0;
|
|
709
|
+
|
|
710
|
+
// Calculate weighted average of section scores
|
|
711
|
+
const totalScore = sections.reduce((sum, section) => sum + section.score, 0);
|
|
712
|
+
const averageScore = totalScore / sections.length;
|
|
713
|
+
|
|
714
|
+
// Apply penalty for critical issues
|
|
715
|
+
const criticalIssues = issues.filter((issue) => issue.severity === IssueSeverity.CRITICAL).length;
|
|
716
|
+
const penalty = criticalIssues * 5;
|
|
717
|
+
|
|
718
|
+
return Math.max(0, Math.round(averageScore - penalty));
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Generate audit summary
|
|
723
|
+
*/
|
|
724
|
+
private generateAuditSummary(sections: AuditSection[], issues: AuditIssue[]): string {
|
|
725
|
+
const totalIssues = issues.length;
|
|
726
|
+
const criticalIssues = issues.filter((i) => i.severity === IssueSeverity.CRITICAL).length;
|
|
727
|
+
const highIssues = issues.filter((i) => i.severity === IssueSeverity.HIGH).length;
|
|
728
|
+
|
|
729
|
+
const passedSections = sections.filter((s) => s.passed).length;
|
|
730
|
+
const totalSections = sections.length;
|
|
731
|
+
|
|
732
|
+
return `Site audit completed: ${passedSections}/${totalSections} sections passed. Found ${totalIssues} issues (${criticalIssues} critical, ${highIssues} high priority). Focus on addressing critical and high-priority issues first for maximum SEO impact.`;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Extract text content from HTML
|
|
737
|
+
*/
|
|
738
|
+
private extractTextContent(html: string): string {
|
|
739
|
+
return html
|
|
740
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
|
|
741
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
|
|
742
|
+
.replace(/<[^>]*>/g, " ")
|
|
743
|
+
.replace(/&[^;]+;/g, " ")
|
|
744
|
+
.replace(/\s+/g, " ")
|
|
745
|
+
.trim();
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Count words in text content
|
|
750
|
+
*/
|
|
751
|
+
private countWords(text: string): number {
|
|
752
|
+
return text.split(/\s+/).filter((word) => word.length > 0).length;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Calculate readability score (simplified Flesch Reading Ease)
|
|
757
|
+
*/
|
|
758
|
+
private calculateReadabilityScore(text: string): number {
|
|
759
|
+
const words = this.countWords(text);
|
|
760
|
+
if (words === 0) return 0;
|
|
761
|
+
|
|
762
|
+
const sentences = text.split(/[.!?]+/).filter((s) => s.trim().length > 0).length;
|
|
763
|
+
const syllables = this.estimateSyllables(text);
|
|
764
|
+
|
|
765
|
+
if (sentences === 0) return 0;
|
|
766
|
+
|
|
767
|
+
const avgWordsPerSentence = words / sentences;
|
|
768
|
+
const avgSyllablesPerWord = syllables / words;
|
|
769
|
+
|
|
770
|
+
// Simplified Flesch Reading Ease formula
|
|
771
|
+
return Math.max(0, Math.min(100, 206.835 - 1.015 * avgWordsPerSentence - 84.6 * avgSyllablesPerWord));
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Estimate syllable count for readability calculation
|
|
776
|
+
*/
|
|
777
|
+
private estimateSyllables(text: string): number {
|
|
778
|
+
const words = text.toLowerCase().match(/\b\w+\b/g) || [];
|
|
779
|
+
|
|
780
|
+
return words.reduce((total, word) => {
|
|
781
|
+
// Simple syllable estimation
|
|
782
|
+
let syllables = word.match(/[aeiouy]+/g)?.length || 1;
|
|
783
|
+
if (word.endsWith("e")) syllables--;
|
|
784
|
+
return total + Math.max(1, syllables);
|
|
785
|
+
}, 0);
|
|
786
|
+
}
|
|
787
|
+
}
|