mcp-wordpress 2.6.4 → 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 +21 -55
- 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
|
@@ -0,0 +1,955 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Generator
|
|
3
|
+
*
|
|
4
|
+
* This module generates JSON-LD structured data markup for WordPress content.
|
|
5
|
+
* It supports all major Schema.org types and provides intelligent content extraction
|
|
6
|
+
* and schema optimization for better search engine understanding.
|
|
7
|
+
*
|
|
8
|
+
* Supported Schema Types:
|
|
9
|
+
* - Article, BlogPosting, NewsArticle
|
|
10
|
+
* - Product, Offer, AggregateRating
|
|
11
|
+
* - FAQ, Question, Answer
|
|
12
|
+
* - HowTo, HowToStep, HowToDirection
|
|
13
|
+
* - Organization, LocalBusiness, Person
|
|
14
|
+
* - Website, WebPage, BreadcrumbList
|
|
15
|
+
* - Event, Recipe, Course, VideoObject
|
|
16
|
+
*
|
|
17
|
+
* @since 2.7.0
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { LoggerFactory } from "../../../utils/logger.js";
|
|
21
|
+
import type { SchemaMarkup, SEOToolParams } from "../../../types/seo.js";
|
|
22
|
+
import type { WordPressPost } from "../../../types/wordpress.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Schema generation options
|
|
26
|
+
*/
|
|
27
|
+
interface SchemaOptions {
|
|
28
|
+
/** Include author information */
|
|
29
|
+
includeAuthor?: boolean;
|
|
30
|
+
|
|
31
|
+
/** Include organization data */
|
|
32
|
+
includeOrganization?: boolean;
|
|
33
|
+
|
|
34
|
+
/** Include breadcrumbs */
|
|
35
|
+
includeBreadcrumbs?: boolean;
|
|
36
|
+
|
|
37
|
+
/** Include images */
|
|
38
|
+
includeImages?: boolean;
|
|
39
|
+
|
|
40
|
+
/** Custom schema properties to merge */
|
|
41
|
+
customProperties?: Record<string, unknown>;
|
|
42
|
+
|
|
43
|
+
/** Site-specific configuration */
|
|
44
|
+
siteConfig?: {
|
|
45
|
+
name?: string;
|
|
46
|
+
url?: string;
|
|
47
|
+
logo?: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
socialProfiles?: string[];
|
|
50
|
+
contactInfo?: {
|
|
51
|
+
telephone?: string;
|
|
52
|
+
email?: string;
|
|
53
|
+
address?: string;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Article schema data structure
|
|
60
|
+
*/
|
|
61
|
+
interface ArticleSchemaData {
|
|
62
|
+
headline: string;
|
|
63
|
+
description: string;
|
|
64
|
+
datePublished: string;
|
|
65
|
+
dateModified: string;
|
|
66
|
+
author: {
|
|
67
|
+
"@type": "Person";
|
|
68
|
+
name: string;
|
|
69
|
+
url?: string;
|
|
70
|
+
};
|
|
71
|
+
publisher: {
|
|
72
|
+
"@type": "Organization";
|
|
73
|
+
name: string;
|
|
74
|
+
logo?: {
|
|
75
|
+
"@type": "ImageObject";
|
|
76
|
+
url: string;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
image?: string[];
|
|
80
|
+
mainEntityOfPage: string;
|
|
81
|
+
wordCount?: number;
|
|
82
|
+
keywords?: string[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Product schema data structure
|
|
87
|
+
*/
|
|
88
|
+
interface ProductSchemaData {
|
|
89
|
+
name: string;
|
|
90
|
+
description: string;
|
|
91
|
+
image?: string[];
|
|
92
|
+
brand?: {
|
|
93
|
+
"@type": "Brand";
|
|
94
|
+
name: string;
|
|
95
|
+
};
|
|
96
|
+
offers?: {
|
|
97
|
+
"@type": "Offer";
|
|
98
|
+
price?: string;
|
|
99
|
+
priceCurrency?: string;
|
|
100
|
+
availability?: string;
|
|
101
|
+
url?: string;
|
|
102
|
+
validFrom?: string;
|
|
103
|
+
validThrough?: string;
|
|
104
|
+
};
|
|
105
|
+
aggregateRating?: {
|
|
106
|
+
"@type": "AggregateRating";
|
|
107
|
+
ratingValue: number;
|
|
108
|
+
reviewCount: number;
|
|
109
|
+
bestRating?: number;
|
|
110
|
+
worstRating?: number;
|
|
111
|
+
};
|
|
112
|
+
review?: Array<{
|
|
113
|
+
"@type": "Review";
|
|
114
|
+
reviewRating: {
|
|
115
|
+
"@type": "Rating";
|
|
116
|
+
ratingValue: number;
|
|
117
|
+
};
|
|
118
|
+
author: {
|
|
119
|
+
"@type": "Person";
|
|
120
|
+
name: string;
|
|
121
|
+
};
|
|
122
|
+
reviewBody: string;
|
|
123
|
+
}>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* FAQ schema data structure
|
|
128
|
+
*/
|
|
129
|
+
interface FAQSchemaData {
|
|
130
|
+
mainEntity: Array<{
|
|
131
|
+
"@type": "Question";
|
|
132
|
+
name: string;
|
|
133
|
+
acceptedAnswer: {
|
|
134
|
+
"@type": "Answer";
|
|
135
|
+
text: string;
|
|
136
|
+
};
|
|
137
|
+
}>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Schema Generator Class
|
|
142
|
+
*/
|
|
143
|
+
export class SchemaGenerator {
|
|
144
|
+
private logger = LoggerFactory.tool("schema_generator");
|
|
145
|
+
|
|
146
|
+
constructor() {}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Generate schema markup for a WordPress post
|
|
150
|
+
*/
|
|
151
|
+
async generateSchema(post: WordPressPost, params: SEOToolParams, options: SchemaOptions = {}): Promise<SchemaMarkup> {
|
|
152
|
+
this.logger.debug("Generating schema markup", {
|
|
153
|
+
postId: post.id,
|
|
154
|
+
schemaType: params.schemaType,
|
|
155
|
+
title: post.title?.rendered?.substring(0, 50),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (!params.schemaType) {
|
|
159
|
+
throw new Error("Schema type is required for schema generation");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const baseSchema: SchemaMarkup = {
|
|
163
|
+
"@context": "https://schema.org",
|
|
164
|
+
"@type": params.schemaType,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Generate schema based on type
|
|
168
|
+
switch (params.schemaType) {
|
|
169
|
+
case "Article":
|
|
170
|
+
return this.generateArticleSchema(post, baseSchema, options);
|
|
171
|
+
|
|
172
|
+
case "Product":
|
|
173
|
+
return this.generateProductSchema(post, baseSchema, options);
|
|
174
|
+
|
|
175
|
+
case "FAQ":
|
|
176
|
+
return this.generateFAQSchema(post, baseSchema, options);
|
|
177
|
+
|
|
178
|
+
case "HowTo":
|
|
179
|
+
return this.generateHowToSchema(post, baseSchema, options);
|
|
180
|
+
|
|
181
|
+
case "Organization":
|
|
182
|
+
return this.generateOrganizationSchema(post, baseSchema, options);
|
|
183
|
+
|
|
184
|
+
case "LocalBusiness":
|
|
185
|
+
return this.generateLocalBusinessSchema(post, baseSchema, options);
|
|
186
|
+
|
|
187
|
+
case "Website":
|
|
188
|
+
return this.generateWebsiteSchema(post, baseSchema, options);
|
|
189
|
+
|
|
190
|
+
case "BreadcrumbList":
|
|
191
|
+
return this.generateBreadcrumbSchema(post, baseSchema, options);
|
|
192
|
+
|
|
193
|
+
case "Event":
|
|
194
|
+
return this.generateEventSchema(post, baseSchema, options);
|
|
195
|
+
|
|
196
|
+
case "Recipe":
|
|
197
|
+
return this.generateRecipeSchema(post, baseSchema, options);
|
|
198
|
+
|
|
199
|
+
case "Course":
|
|
200
|
+
return this.generateCourseSchema(post, baseSchema, options);
|
|
201
|
+
|
|
202
|
+
case "VideoObject":
|
|
203
|
+
return this.generateVideoSchema(post, baseSchema, options);
|
|
204
|
+
|
|
205
|
+
case "Person":
|
|
206
|
+
return this.generatePersonSchema(post, baseSchema, options);
|
|
207
|
+
|
|
208
|
+
case "Review":
|
|
209
|
+
return this.generateReviewSchema(post, baseSchema, options);
|
|
210
|
+
|
|
211
|
+
default:
|
|
212
|
+
throw new Error(`Unsupported schema type: ${params.schemaType}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Generate Article schema markup
|
|
218
|
+
*/
|
|
219
|
+
private generateArticleSchema(post: WordPressPost, baseSchema: SchemaMarkup, options: SchemaOptions): SchemaMarkup {
|
|
220
|
+
const content = this.extractTextContent(post.content?.rendered || "");
|
|
221
|
+
const excerpt = this.extractTextContent(post.excerpt?.rendered || "");
|
|
222
|
+
const images = this.extractImages(post.content?.rendered || "");
|
|
223
|
+
|
|
224
|
+
const articleData: ArticleSchemaData = {
|
|
225
|
+
headline: post.title?.rendered || "Untitled",
|
|
226
|
+
description: excerpt || content.substring(0, 160) + "...",
|
|
227
|
+
datePublished: post.date || new Date().toISOString(),
|
|
228
|
+
dateModified: post.modified || post.date || new Date().toISOString(),
|
|
229
|
+
author: {
|
|
230
|
+
"@type": "Person",
|
|
231
|
+
name: this.getAuthorName(post),
|
|
232
|
+
...(this.getAuthorUrl(post) ? { url: this.getAuthorUrl(post)! } : {}),
|
|
233
|
+
},
|
|
234
|
+
publisher: this.getPublisherInfo(options),
|
|
235
|
+
mainEntityOfPage: post.link || `https://example.com/post/${post.id}`,
|
|
236
|
+
wordCount: this.countWords(content),
|
|
237
|
+
...(images.length > 0 && { image: images }),
|
|
238
|
+
...(options.customProperties?.keywords ? { keywords: options.customProperties.keywords as string[] } : {}),
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
...baseSchema,
|
|
243
|
+
...articleData,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Generate Product schema markup
|
|
249
|
+
*/
|
|
250
|
+
private generateProductSchema(post: WordPressPost, baseSchema: SchemaMarkup, options: SchemaOptions): SchemaMarkup {
|
|
251
|
+
const content = this.extractTextContent(post.content?.rendered || "");
|
|
252
|
+
const excerpt = this.extractTextContent(post.excerpt?.rendered || "");
|
|
253
|
+
const images = this.extractImages(post.content?.rendered || "");
|
|
254
|
+
|
|
255
|
+
// Extract product information from content
|
|
256
|
+
const productInfo = this.extractProductInfo(content);
|
|
257
|
+
|
|
258
|
+
const productData: ProductSchemaData = {
|
|
259
|
+
name: post.title?.rendered || "Untitled Product",
|
|
260
|
+
description: excerpt || content.substring(0, 160) + "...",
|
|
261
|
+
...(images.length > 0 && { image: images }),
|
|
262
|
+
...(productInfo.brand && {
|
|
263
|
+
brand: {
|
|
264
|
+
"@type": "Brand",
|
|
265
|
+
name: productInfo.brand,
|
|
266
|
+
},
|
|
267
|
+
}),
|
|
268
|
+
...(productInfo.price && {
|
|
269
|
+
offers: {
|
|
270
|
+
"@type": "Offer",
|
|
271
|
+
price: productInfo.price,
|
|
272
|
+
priceCurrency: productInfo.currency || "USD",
|
|
273
|
+
availability: productInfo.availability || "https://schema.org/InStock",
|
|
274
|
+
url: post.link || `https://example.com/product/${post.id}`,
|
|
275
|
+
...(productInfo.validFrom && { validFrom: productInfo.validFrom }),
|
|
276
|
+
...(productInfo.validThrough && { validThrough: productInfo.validThrough }),
|
|
277
|
+
},
|
|
278
|
+
}),
|
|
279
|
+
...(productInfo.rating && {
|
|
280
|
+
aggregateRating: {
|
|
281
|
+
"@type": "AggregateRating",
|
|
282
|
+
ratingValue: productInfo.rating.average,
|
|
283
|
+
reviewCount: productInfo.rating.count,
|
|
284
|
+
bestRating: productInfo.rating.best || 5,
|
|
285
|
+
worstRating: productInfo.rating.worst || 1,
|
|
286
|
+
},
|
|
287
|
+
}),
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
...baseSchema,
|
|
292
|
+
...productData,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Generate FAQ schema markup
|
|
298
|
+
*/
|
|
299
|
+
private generateFAQSchema(post: WordPressPost, baseSchema: SchemaMarkup, options: SchemaOptions): SchemaMarkup {
|
|
300
|
+
const content = post.content?.rendered || "";
|
|
301
|
+
const faqItems = this.extractFAQItems(content);
|
|
302
|
+
|
|
303
|
+
if (faqItems.length === 0) {
|
|
304
|
+
this.logger.warn("No FAQ items found in content", { postId: post.id });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const faqData: FAQSchemaData = {
|
|
308
|
+
mainEntity: faqItems.map((item) => ({
|
|
309
|
+
"@type": "Question",
|
|
310
|
+
name: item.question,
|
|
311
|
+
acceptedAnswer: {
|
|
312
|
+
"@type": "Answer",
|
|
313
|
+
text: item.answer,
|
|
314
|
+
},
|
|
315
|
+
})),
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
...baseSchema,
|
|
320
|
+
...faqData,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Generate HowTo schema markup
|
|
326
|
+
*/
|
|
327
|
+
private generateHowToSchema(post: WordPressPost, baseSchema: SchemaMarkup, options: SchemaOptions): SchemaMarkup {
|
|
328
|
+
const content = post.content?.rendered || "";
|
|
329
|
+
const steps = this.extractHowToSteps(content);
|
|
330
|
+
const images = this.extractImages(content);
|
|
331
|
+
const totalTime = this.extractDuration(content);
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
...baseSchema,
|
|
335
|
+
name: post.title?.rendered || "Untitled Guide",
|
|
336
|
+
description: this.extractTextContent(post.excerpt?.rendered || "").substring(0, 160),
|
|
337
|
+
...(images.length > 0 && { image: images }),
|
|
338
|
+
...(totalTime && { totalTime }),
|
|
339
|
+
supply: this.extractSupplies(content),
|
|
340
|
+
tool: this.extractTools(content),
|
|
341
|
+
step: steps.map((step, index) => ({
|
|
342
|
+
"@type": "HowToStep",
|
|
343
|
+
position: index + 1,
|
|
344
|
+
name: step.name,
|
|
345
|
+
text: step.text,
|
|
346
|
+
...(step.image && { image: step.image }),
|
|
347
|
+
})),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Generate Organization schema markup
|
|
353
|
+
*/
|
|
354
|
+
private generateOrganizationSchema(
|
|
355
|
+
post: WordPressPost,
|
|
356
|
+
baseSchema: SchemaMarkup,
|
|
357
|
+
options: SchemaOptions,
|
|
358
|
+
): SchemaMarkup {
|
|
359
|
+
const siteConfig = options.siteConfig || {};
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
...baseSchema,
|
|
363
|
+
name: siteConfig.name || post.title?.rendered || "Organization",
|
|
364
|
+
description: siteConfig.description || this.extractTextContent(post.excerpt?.rendered || ""),
|
|
365
|
+
url: siteConfig.url || post.link || "https://example.com",
|
|
366
|
+
...(siteConfig.logo && {
|
|
367
|
+
logo: {
|
|
368
|
+
"@type": "ImageObject",
|
|
369
|
+
url: siteConfig.logo,
|
|
370
|
+
},
|
|
371
|
+
}),
|
|
372
|
+
...(siteConfig.socialProfiles && { sameAs: siteConfig.socialProfiles }),
|
|
373
|
+
...(siteConfig.contactInfo && {
|
|
374
|
+
contactPoint: {
|
|
375
|
+
"@type": "ContactPoint",
|
|
376
|
+
...(siteConfig.contactInfo.telephone && { telephone: siteConfig.contactInfo.telephone }),
|
|
377
|
+
...(siteConfig.contactInfo.email && { email: siteConfig.contactInfo.email }),
|
|
378
|
+
},
|
|
379
|
+
}),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Generate LocalBusiness schema markup
|
|
385
|
+
*/
|
|
386
|
+
private generateLocalBusinessSchema(
|
|
387
|
+
post: WordPressPost,
|
|
388
|
+
baseSchema: SchemaMarkup,
|
|
389
|
+
options: SchemaOptions,
|
|
390
|
+
): SchemaMarkup {
|
|
391
|
+
const organizationSchema = this.generateOrganizationSchema(post, baseSchema, options);
|
|
392
|
+
const businessInfo = this.extractBusinessInfo(post.content?.rendered || "");
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
...organizationSchema,
|
|
396
|
+
"@type": "LocalBusiness",
|
|
397
|
+
...(businessInfo.address && {
|
|
398
|
+
address: {
|
|
399
|
+
"@type": "PostalAddress",
|
|
400
|
+
streetAddress: businessInfo.address.street,
|
|
401
|
+
addressLocality: businessInfo.address.city,
|
|
402
|
+
addressRegion: businessInfo.address.state,
|
|
403
|
+
postalCode: businessInfo.address.zip,
|
|
404
|
+
addressCountry: businessInfo.address.country,
|
|
405
|
+
},
|
|
406
|
+
}),
|
|
407
|
+
...(businessInfo.phone && { telephone: businessInfo.phone }),
|
|
408
|
+
...(businessInfo.hours && { openingHours: businessInfo.hours }),
|
|
409
|
+
...(businessInfo.priceRange && { priceRange: businessInfo.priceRange }),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Generate Website schema markup
|
|
415
|
+
*/
|
|
416
|
+
private generateWebsiteSchema(post: WordPressPost, baseSchema: SchemaMarkup, options: SchemaOptions): SchemaMarkup {
|
|
417
|
+
const siteConfig = options.siteConfig || {};
|
|
418
|
+
|
|
419
|
+
return {
|
|
420
|
+
...baseSchema,
|
|
421
|
+
name: siteConfig.name || "Website",
|
|
422
|
+
description: siteConfig.description || this.extractTextContent(post.excerpt?.rendered || ""),
|
|
423
|
+
url: siteConfig.url || "https://example.com",
|
|
424
|
+
...(options.includeAuthor && {
|
|
425
|
+
author: {
|
|
426
|
+
"@type": "Organization",
|
|
427
|
+
name: siteConfig.name || "Website Owner",
|
|
428
|
+
},
|
|
429
|
+
}),
|
|
430
|
+
potentialAction: {
|
|
431
|
+
"@type": "SearchAction",
|
|
432
|
+
target: {
|
|
433
|
+
"@type": "EntryPoint",
|
|
434
|
+
urlTemplate: `${siteConfig.url || "https://example.com"}?s={search_term_string}`,
|
|
435
|
+
},
|
|
436
|
+
"query-input": "required name=search_term_string",
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Generate BreadcrumbList schema markup
|
|
443
|
+
*/
|
|
444
|
+
private generateBreadcrumbSchema(
|
|
445
|
+
post: WordPressPost,
|
|
446
|
+
baseSchema: SchemaMarkup,
|
|
447
|
+
options: SchemaOptions,
|
|
448
|
+
): SchemaMarkup {
|
|
449
|
+
// Extract breadcrumb path from post URL or categories
|
|
450
|
+
const breadcrumbs = this.extractBreadcrumbs(post);
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
...baseSchema,
|
|
454
|
+
itemListElement: breadcrumbs.map((breadcrumb, index) => ({
|
|
455
|
+
"@type": "ListItem",
|
|
456
|
+
position: index + 1,
|
|
457
|
+
name: breadcrumb.name,
|
|
458
|
+
item: breadcrumb.url,
|
|
459
|
+
})),
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Generate Event schema markup
|
|
465
|
+
*/
|
|
466
|
+
private generateEventSchema(post: WordPressPost, baseSchema: SchemaMarkup, options: SchemaOptions): SchemaMarkup {
|
|
467
|
+
const eventInfo = this.extractEventInfo(post.content?.rendered || "");
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
...baseSchema,
|
|
471
|
+
name: post.title?.rendered || "Event",
|
|
472
|
+
description: this.extractTextContent(post.excerpt?.rendered || ""),
|
|
473
|
+
startDate: eventInfo.startDate || new Date().toISOString(),
|
|
474
|
+
...(eventInfo.endDate && { endDate: eventInfo.endDate }),
|
|
475
|
+
...(eventInfo.location && {
|
|
476
|
+
location: {
|
|
477
|
+
"@type": "Place",
|
|
478
|
+
name: eventInfo.location.name,
|
|
479
|
+
...(eventInfo.location.address && {
|
|
480
|
+
address: {
|
|
481
|
+
"@type": "PostalAddress",
|
|
482
|
+
streetAddress: eventInfo.location.address,
|
|
483
|
+
},
|
|
484
|
+
}),
|
|
485
|
+
},
|
|
486
|
+
}),
|
|
487
|
+
...(eventInfo.organizer && {
|
|
488
|
+
organizer: {
|
|
489
|
+
"@type": "Organization",
|
|
490
|
+
name: eventInfo.organizer,
|
|
491
|
+
},
|
|
492
|
+
}),
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Generate Recipe schema markup
|
|
498
|
+
*/
|
|
499
|
+
private generateRecipeSchema(post: WordPressPost, baseSchema: SchemaMarkup, options: SchemaOptions): SchemaMarkup {
|
|
500
|
+
const recipeInfo = this.extractRecipeInfo(post.content?.rendered || "");
|
|
501
|
+
const images = this.extractImages(post.content?.rendered || "");
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
...baseSchema,
|
|
505
|
+
name: post.title?.rendered || "Recipe",
|
|
506
|
+
description: this.extractTextContent(post.excerpt?.rendered || ""),
|
|
507
|
+
...(images.length > 0 && { image: images }),
|
|
508
|
+
author: {
|
|
509
|
+
"@type": "Person",
|
|
510
|
+
name: this.getAuthorName(post),
|
|
511
|
+
},
|
|
512
|
+
...(recipeInfo.prepTime && { prepTime: recipeInfo.prepTime }),
|
|
513
|
+
...(recipeInfo.cookTime && { cookTime: recipeInfo.cookTime }),
|
|
514
|
+
...(recipeInfo.totalTime && { totalTime: recipeInfo.totalTime }),
|
|
515
|
+
...(recipeInfo.servings && { recipeYield: recipeInfo.servings }),
|
|
516
|
+
...(recipeInfo.ingredients.length > 0 && { recipeIngredient: recipeInfo.ingredients }),
|
|
517
|
+
...(recipeInfo.instructions.length > 0 && {
|
|
518
|
+
recipeInstructions: recipeInfo.instructions.map((instruction, index) => ({
|
|
519
|
+
"@type": "HowToStep",
|
|
520
|
+
position: index + 1,
|
|
521
|
+
text: instruction,
|
|
522
|
+
})),
|
|
523
|
+
}),
|
|
524
|
+
...(recipeInfo.nutrition && {
|
|
525
|
+
nutrition: {
|
|
526
|
+
"@type": "NutritionInformation",
|
|
527
|
+
calories: recipeInfo.nutrition.calories,
|
|
528
|
+
},
|
|
529
|
+
}),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Generate Course schema markup
|
|
535
|
+
*/
|
|
536
|
+
private generateCourseSchema(post: WordPressPost, baseSchema: SchemaMarkup, options: SchemaOptions): SchemaMarkup {
|
|
537
|
+
const courseInfo = this.extractCourseInfo(post.content?.rendered || "");
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
...baseSchema,
|
|
541
|
+
name: post.title?.rendered || "Course",
|
|
542
|
+
description: this.extractTextContent(post.excerpt?.rendered || ""),
|
|
543
|
+
provider: {
|
|
544
|
+
"@type": "Organization",
|
|
545
|
+
name: options.siteConfig?.name || "Course Provider",
|
|
546
|
+
},
|
|
547
|
+
...(courseInfo.instructor && {
|
|
548
|
+
instructor: {
|
|
549
|
+
"@type": "Person",
|
|
550
|
+
name: courseInfo.instructor,
|
|
551
|
+
},
|
|
552
|
+
}),
|
|
553
|
+
...(courseInfo.duration && { timeRequired: courseInfo.duration }),
|
|
554
|
+
...(courseInfo.level && { courseLevel: courseInfo.level }),
|
|
555
|
+
...(courseInfo.prerequisites.length > 0 && { coursePrerequisites: courseInfo.prerequisites }),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Generate VideoObject schema markup
|
|
561
|
+
*/
|
|
562
|
+
private generateVideoSchema(post: WordPressPost, baseSchema: SchemaMarkup, options: SchemaOptions): SchemaMarkup {
|
|
563
|
+
const videoInfo = this.extractVideoInfo(post.content?.rendered || "");
|
|
564
|
+
|
|
565
|
+
return {
|
|
566
|
+
...baseSchema,
|
|
567
|
+
name: post.title?.rendered || "Video",
|
|
568
|
+
description: this.extractTextContent(post.excerpt?.rendered || ""),
|
|
569
|
+
...(videoInfo.url && { contentUrl: videoInfo.url }),
|
|
570
|
+
...(videoInfo.thumbnail && { thumbnailUrl: videoInfo.thumbnail }),
|
|
571
|
+
...(videoInfo.duration && { duration: videoInfo.duration }),
|
|
572
|
+
uploadDate: post.date || new Date().toISOString(),
|
|
573
|
+
author: {
|
|
574
|
+
"@type": "Person",
|
|
575
|
+
name: this.getAuthorName(post),
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Generate Person schema markup
|
|
582
|
+
*/
|
|
583
|
+
private generatePersonSchema(post: WordPressPost, baseSchema: SchemaMarkup, options: SchemaOptions): SchemaMarkup {
|
|
584
|
+
const personInfo = this.extractPersonInfo(post.content?.rendered || "");
|
|
585
|
+
|
|
586
|
+
return {
|
|
587
|
+
...baseSchema,
|
|
588
|
+
name: post.title?.rendered || "Person",
|
|
589
|
+
description: this.extractTextContent(post.excerpt?.rendered || ""),
|
|
590
|
+
...(personInfo.jobTitle && { jobTitle: personInfo.jobTitle }),
|
|
591
|
+
...(personInfo.affiliation && {
|
|
592
|
+
affiliation: {
|
|
593
|
+
"@type": "Organization",
|
|
594
|
+
name: personInfo.affiliation,
|
|
595
|
+
},
|
|
596
|
+
}),
|
|
597
|
+
...(personInfo.email && { email: personInfo.email }),
|
|
598
|
+
...(personInfo.socialProfiles && { sameAs: personInfo.socialProfiles }),
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Generate Review schema markup
|
|
604
|
+
*/
|
|
605
|
+
private generateReviewSchema(post: WordPressPost, baseSchema: SchemaMarkup, options: SchemaOptions): SchemaMarkup {
|
|
606
|
+
const reviewInfo = this.extractReviewInfo(post.content?.rendered || "");
|
|
607
|
+
|
|
608
|
+
return {
|
|
609
|
+
...baseSchema,
|
|
610
|
+
itemReviewed: {
|
|
611
|
+
"@type": reviewInfo.itemType || "Thing",
|
|
612
|
+
name: reviewInfo.itemName || "Reviewed Item",
|
|
613
|
+
},
|
|
614
|
+
reviewRating: {
|
|
615
|
+
"@type": "Rating",
|
|
616
|
+
ratingValue: reviewInfo.rating || 5,
|
|
617
|
+
bestRating: reviewInfo.bestRating || 5,
|
|
618
|
+
worstRating: reviewInfo.worstRating || 1,
|
|
619
|
+
},
|
|
620
|
+
author: {
|
|
621
|
+
"@type": "Person",
|
|
622
|
+
name: this.getAuthorName(post),
|
|
623
|
+
},
|
|
624
|
+
reviewBody: this.extractTextContent(post.content?.rendered || ""),
|
|
625
|
+
datePublished: post.date || new Date().toISOString(),
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Helper methods for content extraction
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Extract plain text from HTML content
|
|
633
|
+
*/
|
|
634
|
+
private extractTextContent(html: string): string {
|
|
635
|
+
return html
|
|
636
|
+
.replace(/<[^>]*>/g, " ")
|
|
637
|
+
.replace(/\s+/g, " ")
|
|
638
|
+
.trim();
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Extract images from HTML content
|
|
643
|
+
*/
|
|
644
|
+
private extractImages(html: string): string[] {
|
|
645
|
+
const images: string[] = [];
|
|
646
|
+
const imgRegex = /<img[^>]+src="([^"]+)"/gi;
|
|
647
|
+
let match;
|
|
648
|
+
|
|
649
|
+
while ((match = imgRegex.exec(html)) !== null) {
|
|
650
|
+
images.push(match[1]);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return images;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Count words in text
|
|
658
|
+
*/
|
|
659
|
+
private countWords(text: string): number {
|
|
660
|
+
return text.split(/\s+/).filter((word) => word.length > 0).length;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Get author name from post
|
|
665
|
+
*/
|
|
666
|
+
private getAuthorName(post: WordPressPost): string {
|
|
667
|
+
// This would typically come from WordPress API author data
|
|
668
|
+
return "Author"; // Placeholder
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Get author URL from post
|
|
673
|
+
*/
|
|
674
|
+
private getAuthorUrl(post: WordPressPost): string | undefined {
|
|
675
|
+
// This would typically come from WordPress API author data
|
|
676
|
+
return undefined; // Placeholder
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Get publisher information
|
|
681
|
+
*/
|
|
682
|
+
private getPublisherInfo(options: SchemaOptions): ArticleSchemaData["publisher"] {
|
|
683
|
+
const siteConfig = options.siteConfig || {};
|
|
684
|
+
|
|
685
|
+
return {
|
|
686
|
+
"@type": "Organization",
|
|
687
|
+
name: siteConfig.name || "Publisher",
|
|
688
|
+
...(siteConfig.logo && {
|
|
689
|
+
logo: {
|
|
690
|
+
"@type": "ImageObject",
|
|
691
|
+
url: siteConfig.logo,
|
|
692
|
+
},
|
|
693
|
+
}),
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Extract product information from content
|
|
699
|
+
*/
|
|
700
|
+
private extractProductInfo(content: string): {
|
|
701
|
+
brand?: string;
|
|
702
|
+
price?: string;
|
|
703
|
+
currency?: string;
|
|
704
|
+
availability?: string;
|
|
705
|
+
validFrom?: string;
|
|
706
|
+
validThrough?: string;
|
|
707
|
+
rating?: {
|
|
708
|
+
average: number;
|
|
709
|
+
count: number;
|
|
710
|
+
best?: number;
|
|
711
|
+
worst?: number;
|
|
712
|
+
};
|
|
713
|
+
} {
|
|
714
|
+
// Implement product information extraction logic
|
|
715
|
+
return {}; // Placeholder
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Extract FAQ items from content
|
|
720
|
+
*/
|
|
721
|
+
private extractFAQItems(html: string): Array<{ question: string; answer: string }> {
|
|
722
|
+
const faqItems: Array<{ question: string; answer: string }> = [];
|
|
723
|
+
|
|
724
|
+
// Look for FAQ patterns in HTML
|
|
725
|
+
const faqRegex = /<h[23][^>]*>(.*?)<\/h[23]>\s*<p[^>]*>(.*?)<\/p>/gi;
|
|
726
|
+
let match;
|
|
727
|
+
|
|
728
|
+
while ((match = faqRegex.exec(html)) !== null) {
|
|
729
|
+
const question = this.extractTextContent(match[1]);
|
|
730
|
+
const answer = this.extractTextContent(match[2]);
|
|
731
|
+
|
|
732
|
+
if (question && answer) {
|
|
733
|
+
faqItems.push({ question, answer });
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return faqItems;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Extract HowTo steps from content
|
|
742
|
+
*/
|
|
743
|
+
private extractHowToSteps(html: string): Array<{ name: string; text: string; image?: string }> {
|
|
744
|
+
const steps: Array<{ name: string; text: string; image?: string }> = [];
|
|
745
|
+
|
|
746
|
+
// Look for step patterns in HTML
|
|
747
|
+
const stepRegex = /<h[23][^>]*>(.*?)<\/h[23]>\s*<p[^>]*>(.*?)<\/p>/gi;
|
|
748
|
+
let match;
|
|
749
|
+
|
|
750
|
+
while ((match = stepRegex.exec(html)) !== null) {
|
|
751
|
+
const name = this.extractTextContent(match[1]);
|
|
752
|
+
const text = this.extractTextContent(match[2]);
|
|
753
|
+
|
|
754
|
+
if (name && text) {
|
|
755
|
+
steps.push({ name, text });
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return steps;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Extract duration from content
|
|
764
|
+
*/
|
|
765
|
+
private extractDuration(content: string): string | undefined {
|
|
766
|
+
const durationMatch = content.match(/(\d+)\s*(minutes?|hours?|mins?)/i);
|
|
767
|
+
if (durationMatch) {
|
|
768
|
+
const value = parseInt(durationMatch[1]);
|
|
769
|
+
const unit = durationMatch[2].toLowerCase();
|
|
770
|
+
|
|
771
|
+
if (unit.startsWith("min")) {
|
|
772
|
+
return `PT${value}M`;
|
|
773
|
+
} else if (unit.startsWith("hour")) {
|
|
774
|
+
return `PT${value}H`;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return undefined;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Extract supplies from content
|
|
782
|
+
*/
|
|
783
|
+
private extractSupplies(content: string): string[] {
|
|
784
|
+
// Implement supplies extraction logic
|
|
785
|
+
return []; // Placeholder
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Extract tools from content
|
|
790
|
+
*/
|
|
791
|
+
private extractTools(content: string): string[] {
|
|
792
|
+
// Implement tools extraction logic
|
|
793
|
+
return []; // Placeholder
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Extract business information from content
|
|
798
|
+
*/
|
|
799
|
+
private extractBusinessInfo(content: string): {
|
|
800
|
+
address?: {
|
|
801
|
+
street: string;
|
|
802
|
+
city: string;
|
|
803
|
+
state: string;
|
|
804
|
+
zip: string;
|
|
805
|
+
country: string;
|
|
806
|
+
};
|
|
807
|
+
phone?: string;
|
|
808
|
+
hours?: string[];
|
|
809
|
+
priceRange?: string;
|
|
810
|
+
} {
|
|
811
|
+
// Implement business info extraction logic
|
|
812
|
+
return {}; // Placeholder
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Extract breadcrumbs from post
|
|
817
|
+
*/
|
|
818
|
+
private extractBreadcrumbs(post: WordPressPost): Array<{ name: string; url: string }> {
|
|
819
|
+
// Implement breadcrumb extraction logic
|
|
820
|
+
return [
|
|
821
|
+
{ name: "Home", url: "https://example.com" },
|
|
822
|
+
{ name: post.title?.rendered || "Post", url: post.link || "#" },
|
|
823
|
+
]; // Placeholder
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Extract event information from content
|
|
828
|
+
*/
|
|
829
|
+
private extractEventInfo(content: string): {
|
|
830
|
+
startDate?: string;
|
|
831
|
+
endDate?: string;
|
|
832
|
+
location?: {
|
|
833
|
+
name: string;
|
|
834
|
+
address?: string;
|
|
835
|
+
};
|
|
836
|
+
organizer?: string;
|
|
837
|
+
} {
|
|
838
|
+
// Implement event info extraction logic
|
|
839
|
+
return {}; // Placeholder
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Extract recipe information from content
|
|
844
|
+
*/
|
|
845
|
+
private extractRecipeInfo(content: string): {
|
|
846
|
+
prepTime?: string;
|
|
847
|
+
cookTime?: string;
|
|
848
|
+
totalTime?: string;
|
|
849
|
+
servings?: string;
|
|
850
|
+
ingredients: string[];
|
|
851
|
+
instructions: string[];
|
|
852
|
+
nutrition?: {
|
|
853
|
+
calories: string;
|
|
854
|
+
};
|
|
855
|
+
} {
|
|
856
|
+
// Implement recipe info extraction logic
|
|
857
|
+
return {
|
|
858
|
+
ingredients: [],
|
|
859
|
+
instructions: [],
|
|
860
|
+
}; // Placeholder
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Extract course information from content
|
|
865
|
+
*/
|
|
866
|
+
private extractCourseInfo(content: string): {
|
|
867
|
+
instructor?: string;
|
|
868
|
+
duration?: string;
|
|
869
|
+
level?: string;
|
|
870
|
+
prerequisites: string[];
|
|
871
|
+
} {
|
|
872
|
+
// Implement course info extraction logic
|
|
873
|
+
return {
|
|
874
|
+
prerequisites: [],
|
|
875
|
+
}; // Placeholder
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Extract video information from content
|
|
880
|
+
*/
|
|
881
|
+
private extractVideoInfo(content: string): {
|
|
882
|
+
url?: string;
|
|
883
|
+
thumbnail?: string;
|
|
884
|
+
duration?: string;
|
|
885
|
+
} {
|
|
886
|
+
// Implement video info extraction logic
|
|
887
|
+
return {}; // Placeholder
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Extract person information from content
|
|
892
|
+
*/
|
|
893
|
+
private extractPersonInfo(content: string): {
|
|
894
|
+
jobTitle?: string;
|
|
895
|
+
affiliation?: string;
|
|
896
|
+
email?: string;
|
|
897
|
+
socialProfiles?: string[];
|
|
898
|
+
} {
|
|
899
|
+
// Implement person info extraction logic
|
|
900
|
+
return {}; // Placeholder
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Extract review information from content
|
|
905
|
+
*/
|
|
906
|
+
private extractReviewInfo(content: string): {
|
|
907
|
+
itemName?: string;
|
|
908
|
+
itemType?: string;
|
|
909
|
+
rating?: number;
|
|
910
|
+
bestRating?: number;
|
|
911
|
+
worstRating?: number;
|
|
912
|
+
} {
|
|
913
|
+
// Look for rating patterns in content
|
|
914
|
+
const ratingMatch = content.match(/(\d+(?:\.\d+)?)\s*(?:out of|\/)\s*(\d+)/i);
|
|
915
|
+
|
|
916
|
+
if (ratingMatch) {
|
|
917
|
+
return {
|
|
918
|
+
rating: parseFloat(ratingMatch[1]),
|
|
919
|
+
bestRating: parseInt(ratingMatch[2]),
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
return {}; // Placeholder
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Validate generated schema markup
|
|
928
|
+
*/
|
|
929
|
+
validateSchema(schema: SchemaMarkup): { valid: boolean; errors: string[] } {
|
|
930
|
+
const errors: string[] = [];
|
|
931
|
+
|
|
932
|
+
// Basic validation
|
|
933
|
+
if (!schema["@context"]) {
|
|
934
|
+
errors.push("Missing @context");
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (!schema["@type"]) {
|
|
938
|
+
errors.push("Missing @type");
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Type-specific validation
|
|
942
|
+
if (schema["@type"] === "Article" && !schema.headline) {
|
|
943
|
+
errors.push("Article schema missing required headline property");
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
if (schema["@type"] === "Product" && !schema.name) {
|
|
947
|
+
errors.push("Product schema missing required name property");
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return {
|
|
951
|
+
valid: errors.length === 0,
|
|
952
|
+
errors,
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
}
|