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,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Analyzer for SEO
|
|
3
|
+
*
|
|
4
|
+
* Analyzes WordPress content for SEO optimization opportunities including
|
|
5
|
+
* readability scoring, keyword analysis, content structure evaluation,
|
|
6
|
+
* and technical SEO factors.
|
|
7
|
+
*
|
|
8
|
+
* @since 2.7.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { LoggerFactory } from "../../../utils/logger.js";
|
|
12
|
+
import { Config } from "../../../config/Config.js";
|
|
13
|
+
import type { WordPressPost } from "../../../types/wordpress.js";
|
|
14
|
+
import type { SEOAnalysisResult, SEOMetrics, SEORecommendation, SEOToolParams } from "../../../types/seo.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Content analyzer for SEO evaluation
|
|
18
|
+
*/
|
|
19
|
+
export class ContentAnalyzer {
|
|
20
|
+
private logger = LoggerFactory.tool("content_analyzer");
|
|
21
|
+
private config = Config.getInstance().get().seo;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Analyze post content for SEO factors
|
|
25
|
+
*
|
|
26
|
+
* @param post - WordPress post data
|
|
27
|
+
* @param params - Analysis parameters
|
|
28
|
+
* @returns Comprehensive SEO analysis
|
|
29
|
+
*/
|
|
30
|
+
async analyzePost(post: WordPressPost, params: SEOToolParams): Promise<SEOAnalysisResult> {
|
|
31
|
+
const siteLogger = LoggerFactory.tool("seo_analyze_content", params.site);
|
|
32
|
+
|
|
33
|
+
return await siteLogger.time("Content SEO analysis", async () => {
|
|
34
|
+
// Extract content for analysis
|
|
35
|
+
const content = this.extractContent(post);
|
|
36
|
+
const plainText = this.stripHtml(content);
|
|
37
|
+
|
|
38
|
+
// Perform various analyses
|
|
39
|
+
const metrics = await this.calculateMetrics(plainText, content, params);
|
|
40
|
+
const recommendations = await this.generateRecommendations(post, metrics, params);
|
|
41
|
+
const keywordAnalysis = params.focusKeywords?.length
|
|
42
|
+
? await this.analyzeKeywords(plainText, params.focusKeywords[0])
|
|
43
|
+
: undefined;
|
|
44
|
+
const structure = await this.analyzeStructure(content);
|
|
45
|
+
|
|
46
|
+
// Calculate overall SEO score
|
|
47
|
+
const score = this.calculateOverallScore(metrics, recommendations);
|
|
48
|
+
const status = this.getScoreStatus(score);
|
|
49
|
+
|
|
50
|
+
const result: SEOAnalysisResult = {
|
|
51
|
+
score,
|
|
52
|
+
status,
|
|
53
|
+
metrics,
|
|
54
|
+
recommendations,
|
|
55
|
+
structure,
|
|
56
|
+
analyzedAt: new Date().toISOString(),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (keywordAnalysis) {
|
|
60
|
+
result.keywordAnalysis = keywordAnalysis;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return result;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Extract text content from WordPress post
|
|
69
|
+
*
|
|
70
|
+
* @param post - WordPress post
|
|
71
|
+
* @returns Combined content string
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
private extractContent(post: WordPressPost): string {
|
|
75
|
+
const titleContent = typeof post.title === "object" ? post.title.rendered : post.title || "";
|
|
76
|
+
const bodyContent = typeof post.content === "object" ? post.content.rendered : post.content || "";
|
|
77
|
+
const excerptContent = typeof post.excerpt === "object" ? post.excerpt.rendered : post.excerpt || "";
|
|
78
|
+
|
|
79
|
+
return `${titleContent} ${bodyContent} ${excerptContent}`.trim();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Strip HTML tags and return plain text
|
|
84
|
+
*
|
|
85
|
+
* @param html - HTML content
|
|
86
|
+
* @returns Plain text
|
|
87
|
+
* @private
|
|
88
|
+
*/
|
|
89
|
+
private stripHtml(html: string): string {
|
|
90
|
+
// Remove HTML tags, scripts, and styles
|
|
91
|
+
return html
|
|
92
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
|
|
93
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
|
|
94
|
+
.replace(/<[^>]*>/g, "")
|
|
95
|
+
.replace(/ /g, " ")
|
|
96
|
+
.replace(/&[#\w]+;/g, "")
|
|
97
|
+
.replace(/\s+/g, " ")
|
|
98
|
+
.trim();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Calculate comprehensive content metrics
|
|
103
|
+
*
|
|
104
|
+
* @param plainText - Plain text content
|
|
105
|
+
* @param htmlContent - Original HTML content
|
|
106
|
+
* @param params - Analysis parameters
|
|
107
|
+
* @returns SEO metrics
|
|
108
|
+
* @private
|
|
109
|
+
*/
|
|
110
|
+
private async calculateMetrics(plainText: string, htmlContent: string, params: SEOToolParams): Promise<SEOMetrics> {
|
|
111
|
+
const words = this.getWords(plainText);
|
|
112
|
+
const sentences = this.getSentences(plainText);
|
|
113
|
+
const syllables = this.countSyllables(plainText);
|
|
114
|
+
|
|
115
|
+
// Calculate readability scores
|
|
116
|
+
const avgWordsPerSentence = words.length / Math.max(sentences.length, 1);
|
|
117
|
+
const avgSyllablesPerWord = syllables / Math.max(words.length, 1);
|
|
118
|
+
|
|
119
|
+
const fleschReadingEase = this.calculateFleschReadingEase(avgWordsPerSentence, avgSyllablesPerWord);
|
|
120
|
+
|
|
121
|
+
const fleschKincaidGrade = this.calculateFleschKincaidGrade(avgWordsPerSentence, avgSyllablesPerWord);
|
|
122
|
+
|
|
123
|
+
// Keyword density for focus keyword
|
|
124
|
+
const keywordDensity = params.focusKeywords?.length
|
|
125
|
+
? this.calculateKeywordDensity(plainText, params.focusKeywords[0])
|
|
126
|
+
: 0;
|
|
127
|
+
|
|
128
|
+
// HTML structure analysis
|
|
129
|
+
const headings = this.countHeadings(htmlContent);
|
|
130
|
+
const links = this.countLinks(htmlContent);
|
|
131
|
+
const images = this.countImages(htmlContent);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
wordCount: words.length,
|
|
135
|
+
avgWordsPerSentence,
|
|
136
|
+
avgSyllablesPerWord,
|
|
137
|
+
fleschReadingEase,
|
|
138
|
+
fleschKincaidGrade,
|
|
139
|
+
keywordDensity,
|
|
140
|
+
headingCount: headings.total,
|
|
141
|
+
internalLinkCount: links.internal,
|
|
142
|
+
externalLinkCount: links.external,
|
|
143
|
+
imageCount: images.total,
|
|
144
|
+
imagesWithAltText: images.withAlt,
|
|
145
|
+
readingTime: Math.ceil(words.length / 200), // Assume 200 WPM reading speed
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Generate SEO recommendations based on analysis
|
|
151
|
+
*
|
|
152
|
+
* @param post - WordPress post
|
|
153
|
+
* @param metrics - Calculated metrics
|
|
154
|
+
* @param params - Analysis parameters
|
|
155
|
+
* @returns Array of recommendations
|
|
156
|
+
* @private
|
|
157
|
+
*/
|
|
158
|
+
private async generateRecommendations(
|
|
159
|
+
post: WordPressPost,
|
|
160
|
+
metrics: SEOMetrics,
|
|
161
|
+
params: SEOToolParams,
|
|
162
|
+
): Promise<SEORecommendation[]> {
|
|
163
|
+
const recommendations: SEORecommendation[] = [];
|
|
164
|
+
|
|
165
|
+
// Word count recommendations
|
|
166
|
+
if (metrics.wordCount < this.config.analysis.minWordCount) {
|
|
167
|
+
recommendations.push({
|
|
168
|
+
type: "content",
|
|
169
|
+
priority: "high",
|
|
170
|
+
message: `Content is too short (${metrics.wordCount} words). Aim for at least ${this.config.analysis.minWordCount} words for better SEO.`,
|
|
171
|
+
impact: 80,
|
|
172
|
+
autoFixAvailable: false,
|
|
173
|
+
helpUrl: "https://developers.google.com/search/docs/fundamentals/creating-helpful-content",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Readability recommendations
|
|
178
|
+
if (metrics.fleschReadingEase < this.config.analysis.minReadabilityScore) {
|
|
179
|
+
recommendations.push({
|
|
180
|
+
type: "content",
|
|
181
|
+
priority: "medium",
|
|
182
|
+
message: `Content readability is low (${metrics.fleschReadingEase.toFixed(1)}). Consider using shorter sentences and simpler words.`,
|
|
183
|
+
impact: 60,
|
|
184
|
+
autoFixAvailable: false,
|
|
185
|
+
suggestedFix: "Break up long sentences and use more common vocabulary.",
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Keyword density recommendations
|
|
190
|
+
if (params.focusKeywords?.length) {
|
|
191
|
+
const targetDensity = this.config.analysis.targetKeywordDensity;
|
|
192
|
+
const maxDensity = this.config.analysis.maxKeywordDensity;
|
|
193
|
+
|
|
194
|
+
if (metrics.keywordDensity < targetDensity) {
|
|
195
|
+
recommendations.push({
|
|
196
|
+
type: "keyword",
|
|
197
|
+
priority: "medium",
|
|
198
|
+
message: `Focus keyword density is low (${metrics.keywordDensity.toFixed(1)}%). Consider adding the keyword "${params.focusKeywords[0]}" more naturally throughout the content.`,
|
|
199
|
+
impact: 70,
|
|
200
|
+
autoFixAvailable: false,
|
|
201
|
+
});
|
|
202
|
+
} else if (metrics.keywordDensity > maxDensity) {
|
|
203
|
+
recommendations.push({
|
|
204
|
+
type: "keyword",
|
|
205
|
+
priority: "medium",
|
|
206
|
+
message: `Focus keyword density is too high (${metrics.keywordDensity.toFixed(1)}%). This might be considered keyword stuffing.`,
|
|
207
|
+
impact: 75,
|
|
208
|
+
autoFixAvailable: false,
|
|
209
|
+
suggestedFix: `Reduce keyword usage to around ${targetDensity}%`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Image recommendations
|
|
215
|
+
if (metrics.imageCount > 0 && metrics.imagesWithAltText < metrics.imageCount) {
|
|
216
|
+
const missingAlt = metrics.imageCount - metrics.imagesWithAltText;
|
|
217
|
+
recommendations.push({
|
|
218
|
+
type: "technical",
|
|
219
|
+
priority: "high",
|
|
220
|
+
message: `${missingAlt} image(s) missing alt text. Alt text improves accessibility and SEO.`,
|
|
221
|
+
impact: 85,
|
|
222
|
+
autoFixAvailable: false,
|
|
223
|
+
suggestedFix: "Add descriptive alt text to all images",
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Heading structure recommendations
|
|
228
|
+
if (metrics.headingCount === 0) {
|
|
229
|
+
recommendations.push({
|
|
230
|
+
type: "structure",
|
|
231
|
+
priority: "high",
|
|
232
|
+
message: "No headings found. Use H1-H6 tags to structure your content.",
|
|
233
|
+
impact: 90,
|
|
234
|
+
autoFixAvailable: false,
|
|
235
|
+
suggestedFix: "Add at least one H1 heading and use H2-H6 for subheadings",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Internal linking recommendations
|
|
240
|
+
if (metrics.internalLinkCount === 0) {
|
|
241
|
+
recommendations.push({
|
|
242
|
+
type: "structure",
|
|
243
|
+
priority: "medium",
|
|
244
|
+
message: "No internal links found. Internal linking helps with site navigation and SEO.",
|
|
245
|
+
impact: 65,
|
|
246
|
+
autoFixAvailable: false,
|
|
247
|
+
suggestedFix: "Add 2-3 relevant internal links to other pages on your site",
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return recommendations;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Analyze focus keyword usage
|
|
256
|
+
*
|
|
257
|
+
* @param plainText - Plain text content
|
|
258
|
+
* @param keyword - Focus keyword
|
|
259
|
+
* @returns Keyword analysis
|
|
260
|
+
* @private
|
|
261
|
+
*/
|
|
262
|
+
private async analyzeKeywords(
|
|
263
|
+
plainText: string,
|
|
264
|
+
keyword: string,
|
|
265
|
+
): Promise<{
|
|
266
|
+
primaryKeyword: string;
|
|
267
|
+
keywordFound: boolean;
|
|
268
|
+
occurrences: number;
|
|
269
|
+
density: number;
|
|
270
|
+
semanticKeywords: string[];
|
|
271
|
+
competitorGap?: string[];
|
|
272
|
+
}> {
|
|
273
|
+
const lowerText = plainText.toLowerCase();
|
|
274
|
+
const lowerKeyword = keyword.toLowerCase();
|
|
275
|
+
const words = this.getWords(plainText);
|
|
276
|
+
|
|
277
|
+
// Count exact keyword occurrences
|
|
278
|
+
const keywordRegex = new RegExp(`\\b${lowerKeyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "gi");
|
|
279
|
+
const matches = lowerText.match(keywordRegex) || [];
|
|
280
|
+
const occurrences = matches.length;
|
|
281
|
+
|
|
282
|
+
// Calculate keyword density
|
|
283
|
+
const density = (occurrences / words.length) * 100;
|
|
284
|
+
|
|
285
|
+
// Find semantic keywords (simple implementation)
|
|
286
|
+
const semanticKeywords = this.findSemanticKeywords(plainText, keyword);
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
primaryKeyword: keyword,
|
|
290
|
+
keywordFound: occurrences > 0,
|
|
291
|
+
occurrences,
|
|
292
|
+
density,
|
|
293
|
+
semanticKeywords,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Analyze content structure
|
|
299
|
+
*
|
|
300
|
+
* @param htmlContent - HTML content
|
|
301
|
+
* @returns Structure analysis
|
|
302
|
+
* @private
|
|
303
|
+
*/
|
|
304
|
+
private async analyzeStructure(htmlContent: string): Promise<{
|
|
305
|
+
hasH1: boolean;
|
|
306
|
+
h1Text: string;
|
|
307
|
+
headingHierarchy: boolean;
|
|
308
|
+
paragraphCount: number;
|
|
309
|
+
avgParagraphLength: number;
|
|
310
|
+
}> {
|
|
311
|
+
const h1Match = htmlContent.match(/<h1[^>]*>(.*?)<\/h1>/i);
|
|
312
|
+
const h1Text = h1Match ? this.stripHtml(h1Match[1]) : "";
|
|
313
|
+
const hasH1 = Boolean(h1Match);
|
|
314
|
+
|
|
315
|
+
// Check heading hierarchy (simplified)
|
|
316
|
+
const headingHierarchy = this.checkHeadingHierarchy(htmlContent);
|
|
317
|
+
|
|
318
|
+
// Analyze paragraphs
|
|
319
|
+
const paragraphs = htmlContent.match(/<p[^>]*>[\s\S]*?<\/p>/gi) || [];
|
|
320
|
+
const paragraphTexts = paragraphs.map((p) => this.stripHtml(p)).filter((text) => text.trim().length > 0);
|
|
321
|
+
const avgParagraphLength =
|
|
322
|
+
paragraphTexts.length > 0
|
|
323
|
+
? paragraphTexts.reduce((sum, text) => sum + this.getWords(text).length, 0) / paragraphTexts.length
|
|
324
|
+
: 0;
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
hasH1,
|
|
328
|
+
h1Text,
|
|
329
|
+
headingHierarchy,
|
|
330
|
+
paragraphCount: paragraphTexts.length,
|
|
331
|
+
avgParagraphLength,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Helper methods
|
|
336
|
+
|
|
337
|
+
private getWords(text: string): string[] {
|
|
338
|
+
return text.toLowerCase().match(/\b\w+\b/g) || [];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private getSentences(text: string): string[] {
|
|
342
|
+
return text.split(/[.!?]+/).filter((s) => s.trim().length > 0);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private countSyllables(text: string): number {
|
|
346
|
+
const words = this.getWords(text);
|
|
347
|
+
return words.reduce((total, word) => {
|
|
348
|
+
// Simple syllable counting heuristic
|
|
349
|
+
const vowels = word.match(/[aeiouy]+/g);
|
|
350
|
+
let count = vowels ? vowels.length : 0;
|
|
351
|
+
if (word.endsWith("e")) count--;
|
|
352
|
+
return total + Math.max(1, count);
|
|
353
|
+
}, 0);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private calculateFleschReadingEase(avgWordsPerSentence: number, avgSyllablesPerWord: number): number {
|
|
357
|
+
return 206.835 - 1.015 * avgWordsPerSentence - 84.6 * avgSyllablesPerWord;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private calculateFleschKincaidGrade(avgWordsPerSentence: number, avgSyllablesPerWord: number): number {
|
|
361
|
+
return 0.39 * avgWordsPerSentence + 11.8 * avgSyllablesPerWord - 15.59;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private calculateKeywordDensity(text: string, keyword: string): number {
|
|
365
|
+
const words = this.getWords(text);
|
|
366
|
+
const keywordWords = this.getWords(keyword.toLowerCase());
|
|
367
|
+
|
|
368
|
+
if (words.length === 0 || keywordWords.length === 0) return 0;
|
|
369
|
+
|
|
370
|
+
// Count occurrences of the exact keyword phrase
|
|
371
|
+
const textString = words.join(" ");
|
|
372
|
+
const keywordString = keywordWords.join(" ");
|
|
373
|
+
const regex = new RegExp(keywordString.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
|
|
374
|
+
const matches = textString.match(regex) || [];
|
|
375
|
+
|
|
376
|
+
return (matches.length / words.length) * 100;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private countHeadings(htmlContent: string): { total: number; byLevel: Record<string, number> } {
|
|
380
|
+
const byLevel: Record<string, number> = {};
|
|
381
|
+
let total = 0;
|
|
382
|
+
|
|
383
|
+
for (let i = 1; i <= 6; i++) {
|
|
384
|
+
const regex = new RegExp(`<h${i}[^>]*>.*?</h${i}>`, "gi");
|
|
385
|
+
const matches = htmlContent.match(regex) || [];
|
|
386
|
+
byLevel[`h${i}`] = matches.length;
|
|
387
|
+
total += matches.length;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return { total, byLevel };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private countLinks(htmlContent: string): { internal: number; external: number; total: number } {
|
|
394
|
+
const linkRegex = /<a[^>]+href=["']([^"']+)["'][^>]*>/gi;
|
|
395
|
+
const links = [];
|
|
396
|
+
let match;
|
|
397
|
+
|
|
398
|
+
while ((match = linkRegex.exec(htmlContent)) !== null) {
|
|
399
|
+
links.push(match[1]);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const internal = links.filter(
|
|
403
|
+
(href) => href.startsWith("/") || href.startsWith("#") || href.includes(this.getCurrentDomain()),
|
|
404
|
+
).length;
|
|
405
|
+
|
|
406
|
+
const external = links.length - internal;
|
|
407
|
+
|
|
408
|
+
return { internal, external, total: links.length };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private countImages(htmlContent: string): { total: number; withAlt: number } {
|
|
412
|
+
const imageRegex = /<img[^>]*>/gi;
|
|
413
|
+
const images = htmlContent.match(imageRegex) || [];
|
|
414
|
+
|
|
415
|
+
const withAlt = images.filter((img) => /alt\s*=\s*["'][^"']+["']/i.test(img)).length;
|
|
416
|
+
|
|
417
|
+
return { total: images.length, withAlt };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private findSemanticKeywords(text: string, mainKeyword: string): string[] {
|
|
421
|
+
// Simple semantic keyword detection
|
|
422
|
+
// In a real implementation, this would use NLP libraries or APIs
|
|
423
|
+
const _words = this.getWords(text);
|
|
424
|
+
const _mainKeywordWords = this.getWords(mainKeyword.toLowerCase());
|
|
425
|
+
|
|
426
|
+
// Find words that commonly appear with the main keyword
|
|
427
|
+
const semanticKeywords: string[] = [];
|
|
428
|
+
|
|
429
|
+
// This is a simplified implementation
|
|
430
|
+
// Real semantic analysis would use word embeddings, LSI, or similar techniques
|
|
431
|
+
|
|
432
|
+
return semanticKeywords.slice(0, 10); // Return top 10 semantic keywords
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private checkHeadingHierarchy(htmlContent: string): boolean {
|
|
436
|
+
const headingMatches = htmlContent.match(/<h([1-6])[^>]*>/gi);
|
|
437
|
+
if (!headingMatches) return true;
|
|
438
|
+
|
|
439
|
+
const levels = headingMatches.map((match) => {
|
|
440
|
+
const levelMatch = match.match(/<h([1-6])/i);
|
|
441
|
+
return levelMatch ? parseInt(levelMatch[1]) : 0;
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Check if headings follow proper hierarchy (no skipping levels)
|
|
445
|
+
let currentLevel = 0;
|
|
446
|
+
for (const level of levels) {
|
|
447
|
+
if (level > currentLevel + 1) {
|
|
448
|
+
return false; // Skipped a level
|
|
449
|
+
}
|
|
450
|
+
currentLevel = Math.max(currentLevel, level);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private getCurrentDomain(): string {
|
|
457
|
+
// This would need to be determined from the WordPress site URL
|
|
458
|
+
// For testing purposes, return a default domain
|
|
459
|
+
return "localhost";
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private calculateOverallScore(metrics: SEOMetrics, recommendations: SEORecommendation[]): number {
|
|
463
|
+
let score = 100;
|
|
464
|
+
|
|
465
|
+
// Deduct points for each recommendation based on impact
|
|
466
|
+
for (const rec of recommendations) {
|
|
467
|
+
const deduction = rec.impact * 0.3; // Scale impact to reasonable deduction
|
|
468
|
+
score -= deduction;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Bonus points for good metrics
|
|
472
|
+
if (metrics.wordCount >= this.config.analysis.minWordCount) {
|
|
473
|
+
score += 5;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (metrics.fleschReadingEase >= this.config.analysis.minReadabilityScore) {
|
|
477
|
+
score += 5;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (metrics.imagesWithAltText === metrics.imageCount && metrics.imageCount > 0) {
|
|
481
|
+
score += 5;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return Math.max(0, Math.min(100, Math.round(score)));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
private getScoreStatus(score: number): "poor" | "needs-improvement" | "good" | "excellent" {
|
|
488
|
+
if (score >= 90) return "excellent";
|
|
489
|
+
if (score >= 70) return "good";
|
|
490
|
+
if (score >= 50) return "needs-improvement";
|
|
491
|
+
return "poor";
|
|
492
|
+
}
|
|
493
|
+
}
|