mcp-wordpress 2.4.2 → 2.5.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 +114 -48
- package/dist/ajv-patch.js +34 -0
- package/dist/cache/CacheInvalidation.d.ts +3 -1
- package/dist/cache/CacheInvalidation.d.ts.map +1 -1
- package/dist/cache/CacheInvalidation.js +10 -4
- package/dist/cache/CacheInvalidation.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +3 -2
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +11 -3
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/HttpCacheWrapper.d.ts +7 -6
- package/dist/cache/HttpCacheWrapper.d.ts.map +1 -1
- package/dist/cache/HttpCacheWrapper.js +8 -5
- package/dist/cache/HttpCacheWrapper.js.map +1 -1
- package/dist/cache/__tests__/HttpCacheWrapper.test.js +6 -5
- package/dist/cache/__tests__/HttpCacheWrapper.test.js.map +1 -1
- package/dist/cache/index.d.ts +3 -3
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/client/CachedWordPressClient.d.ts +23 -9
- package/dist/client/CachedWordPressClient.d.ts.map +1 -1
- package/dist/client/CachedWordPressClient.js +4 -1
- package/dist/client/CachedWordPressClient.js.map +1 -1
- package/dist/client/MockWordPressClient.d.ts +2 -1
- package/dist/client/MockWordPressClient.d.ts.map +1 -1
- package/dist/client/MockWordPressClient.js +3 -1
- package/dist/client/MockWordPressClient.js.map +1 -1
- package/dist/client/api.d.ts +17 -13
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/api.js +135 -30
- package/dist/client/api.js.map +1 -1
- package/dist/client/auth.d.ts.map +1 -1
- package/dist/client/auth.js +2 -3
- package/dist/client/auth.js.map +1 -1
- package/dist/client/managers/AuthenticationManager.d.ts +55 -2
- package/dist/client/managers/AuthenticationManager.d.ts.map +1 -1
- package/dist/client/managers/AuthenticationManager.js +269 -71
- package/dist/client/managers/AuthenticationManager.js.map +1 -1
- package/dist/client/managers/BaseManager.d.ts +3 -3
- package/dist/client/managers/BaseManager.d.ts.map +1 -1
- package/dist/client/managers/BaseManager.js +11 -5
- package/dist/client/managers/BaseManager.js.map +1 -1
- package/dist/client/managers/RequestManager.d.ts +2 -2
- package/dist/client/managers/RequestManager.d.ts.map +1 -1
- package/dist/client/managers/RequestManager.js +25 -12
- package/dist/client/managers/RequestManager.js.map +1 -1
- package/dist/config/Config.d.ts +155 -0
- package/dist/config/Config.d.ts.map +1 -0
- package/dist/config/Config.js +215 -0
- package/dist/config/Config.js.map +1 -0
- package/dist/config/ConfigurationSchema.d.ts +21 -21
- package/dist/config/ConfigurationSchema.d.ts.map +1 -1
- package/dist/config/ConfigurationSchema.js +19 -2
- package/dist/config/ConfigurationSchema.js.map +1 -1
- package/dist/config/ServerConfiguration.d.ts +2 -1
- package/dist/config/ServerConfiguration.d.ts.map +1 -1
- package/dist/config/ServerConfiguration.js +50 -41
- package/dist/config/ServerConfiguration.js.map +1 -1
- package/dist/docs/DocumentationGenerator.d.ts +9 -8
- package/dist/docs/DocumentationGenerator.d.ts.map +1 -1
- package/dist/docs/DocumentationGenerator.js +10 -7
- package/dist/docs/DocumentationGenerator.js.map +1 -1
- package/dist/docs/MarkdownFormatter.d.ts.map +1 -1
- package/dist/docs/MarkdownFormatter.js +3 -2
- package/dist/docs/MarkdownFormatter.js.map +1 -1
- package/dist/dxt-entry.cjs +81 -0
- package/dist/dxt-entry.js +15 -14
- package/dist/dxt-entry.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -21
- package/dist/index.js.map +1 -1
- package/dist/performance/MetricsCollector.d.ts +13 -7
- package/dist/performance/MetricsCollector.d.ts.map +1 -1
- package/dist/performance/MetricsCollector.js +69 -27
- package/dist/performance/MetricsCollector.js.map +1 -1
- package/dist/performance/PerformanceAnalytics.d.ts +8 -2
- package/dist/performance/PerformanceAnalytics.d.ts.map +1 -1
- package/dist/performance/PerformanceAnalytics.js +17 -47
- package/dist/performance/PerformanceAnalytics.js.map +1 -1
- package/dist/performance/PerformanceMonitor.d.ts +2 -1
- package/dist/performance/PerformanceMonitor.d.ts.map +1 -1
- package/dist/performance/PerformanceMonitor.js +12 -13
- package/dist/performance/PerformanceMonitor.js.map +1 -1
- package/dist/performance/index.d.ts +2 -2
- package/dist/performance/index.d.ts.map +1 -1
- package/dist/security/AISecurityScanner.d.ts +1 -0
- package/dist/security/AISecurityScanner.d.ts.map +1 -1
- package/dist/security/AISecurityScanner.js +22 -12
- package/dist/security/AISecurityScanner.js.map +1 -1
- package/dist/security/AutomatedRemediation.d.ts +4 -3
- package/dist/security/AutomatedRemediation.d.ts.map +1 -1
- package/dist/security/AutomatedRemediation.js +46 -15
- package/dist/security/AutomatedRemediation.js.map +1 -1
- package/dist/security/InputValidator.d.ts +13 -9
- package/dist/security/InputValidator.d.ts.map +1 -1
- package/dist/security/InputValidator.js +4 -2
- package/dist/security/InputValidator.js.map +1 -1
- package/dist/security/SecurityCIPipeline.d.ts +1 -1
- package/dist/security/SecurityCIPipeline.d.ts.map +1 -1
- package/dist/security/SecurityCIPipeline.js +38 -29
- package/dist/security/SecurityCIPipeline.js.map +1 -1
- package/dist/security/SecurityConfig.d.ts +3 -3
- package/dist/security/SecurityConfig.d.ts.map +1 -1
- package/dist/security/SecurityConfig.js +13 -9
- package/dist/security/SecurityConfig.js.map +1 -1
- package/dist/security/SecurityConfigManager.d.ts +2 -2
- package/dist/security/SecurityConfigManager.d.ts.map +1 -1
- package/dist/security/SecurityConfigManager.js +20 -15
- package/dist/security/SecurityConfigManager.js.map +1 -1
- package/dist/security/SecurityMonitoring.d.ts +2 -2
- package/dist/security/SecurityMonitoring.d.ts.map +1 -1
- package/dist/security/SecurityMonitoring.js +19 -17
- package/dist/security/SecurityMonitoring.js.map +1 -1
- package/dist/security/SecurityReviewer.d.ts.map +1 -1
- package/dist/security/SecurityReviewer.js +10 -7
- package/dist/security/SecurityReviewer.js.map +1 -1
- package/dist/security/index.d.ts +24 -23
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +52 -23
- package/dist/security/index.js.map +1 -1
- package/dist/server/ConnectionTester.d.ts +12 -4
- package/dist/server/ConnectionTester.d.ts.map +1 -1
- package/dist/server/ConnectionTester.js +96 -22
- 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 +10 -5
- package/dist/server/ToolRegistry.js.map +1 -1
- package/dist/tools/BaseToolManager.d.ts +47 -11
- package/dist/tools/BaseToolManager.d.ts.map +1 -1
- package/dist/tools/BaseToolManager.js +168 -29
- package/dist/tools/BaseToolManager.js.map +1 -1
- package/dist/tools/auth.d.ts +16 -10
- package/dist/tools/auth.d.ts.map +1 -1
- package/dist/tools/auth.js +3 -2
- package/dist/tools/auth.js.map +1 -1
- package/dist/tools/cache.d.ts +30 -30
- package/dist/tools/cache.d.ts.map +1 -1
- package/dist/tools/cache.js +1 -6
- package/dist/tools/cache.js.map +1 -1
- package/dist/tools/comments.d.ts +20 -20
- package/dist/tools/comments.d.ts.map +1 -1
- package/dist/tools/comments.js +16 -9
- package/dist/tools/comments.js.map +1 -1
- package/dist/tools/media.d.ts +18 -16
- package/dist/tools/media.d.ts.map +1 -1
- package/dist/tools/media.js +16 -15
- package/dist/tools/media.js.map +1 -1
- package/dist/tools/pages.d.ts +19 -17
- package/dist/tools/pages.d.ts.map +1 -1
- package/dist/tools/pages.js +16 -12
- package/dist/tools/pages.js.map +1 -1
- package/dist/tools/performance.d.ts +11 -1
- package/dist/tools/performance.d.ts.map +1 -1
- package/dist/tools/performance.js +67 -34
- package/dist/tools/performance.js.map +1 -1
- package/dist/tools/posts/PostHandlers.d.ts +46 -0
- package/dist/tools/posts/PostHandlers.d.ts.map +1 -0
- package/dist/tools/posts/PostHandlers.js +400 -0
- package/dist/tools/posts/PostHandlers.js.map +1 -0
- package/dist/tools/posts/PostToolDefinitions.d.ts +37 -0
- package/dist/tools/posts/PostToolDefinitions.d.ts.map +1 -0
- package/dist/tools/posts/PostToolDefinitions.js +236 -0
- package/dist/tools/posts/PostToolDefinitions.js.map +1 -0
- package/dist/tools/posts/index.d.ts +138 -0
- package/dist/tools/posts/index.d.ts.map +1 -0
- package/dist/tools/posts/index.js +163 -0
- package/dist/tools/posts/index.js.map +1 -0
- package/dist/tools/posts.d.ts +10 -246
- package/dist/tools/posts.d.ts.map +1 -1
- package/dist/tools/posts.js +11 -723
- package/dist/tools/posts.js.map +1 -1
- package/dist/tools/site.d.ts +19 -18
- package/dist/tools/site.d.ts.map +1 -1
- package/dist/tools/site.js +14 -10
- package/dist/tools/site.js.map +1 -1
- package/dist/tools/taxonomies.d.ts +23 -24
- package/dist/tools/taxonomies.d.ts.map +1 -1
- package/dist/tools/taxonomies.js +24 -18
- package/dist/tools/taxonomies.js.map +1 -1
- package/dist/tools/users.d.ts +20 -15
- package/dist/tools/users.d.ts.map +1 -1
- package/dist/tools/users.js +12 -8
- package/dist/tools/users.js.map +1 -1
- package/dist/types/client.d.ts +48 -41
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/client.js +30 -5
- package/dist/types/client.js.map +1 -1
- package/dist/types/enhanced.d.ts +237 -0
- package/dist/types/enhanced.d.ts.map +1 -0
- package/dist/types/enhanced.js +49 -0
- package/dist/types/enhanced.js.map +1 -0
- package/dist/types/index.d.ts +15 -12
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/mcp.d.ts +12 -12
- package/dist/types/mcp.d.ts.map +1 -1
- package/dist/types/requests.d.ts +322 -0
- package/dist/types/requests.d.ts.map +1 -0
- package/dist/types/requests.js +8 -0
- package/dist/types/requests.js.map +1 -0
- package/dist/types/tools.d.ts +506 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +8 -0
- package/dist/types/tools.js.map +1 -0
- package/dist/types/wordpress.d.ts +43 -15
- package/dist/types/wordpress.d.ts.map +1 -1
- package/dist/types/wordpress.js +8 -1
- package/dist/types/wordpress.js.map +1 -1
- package/dist/utils/debug.d.ts +19 -11
- package/dist/utils/debug.d.ts.map +1 -1
- package/dist/utils/debug.js +46 -10
- package/dist/utils/debug.js.map +1 -1
- package/dist/utils/enhancedError.d.ts +8 -8
- package/dist/utils/enhancedError.d.ts.map +1 -1
- package/dist/utils/enhancedError.js.map +1 -1
- package/dist/utils/error.d.ts +2 -4
- package/dist/utils/error.d.ts.map +1 -1
- package/dist/utils/error.js +42 -5
- package/dist/utils/error.js.map +1 -1
- package/dist/utils/logger.d.ts +106 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +280 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/streaming.d.ts +9 -9
- package/dist/utils/streaming.d.ts.map +1 -1
- package/dist/utils/streaming.js +71 -52
- package/dist/utils/streaming.js.map +1 -1
- package/dist/utils/toolWrapper.d.ts +9 -7
- package/dist/utils/toolWrapper.d.ts.map +1 -1
- package/dist/utils/toolWrapper.js.map +1 -1
- package/dist/utils/validation/core.d.ts +21 -0
- package/dist/utils/validation/core.d.ts.map +1 -0
- package/dist/utils/validation/core.js +71 -0
- package/dist/utils/validation/core.js.map +1 -0
- package/dist/utils/validation/index.d.ts +25 -0
- package/dist/utils/validation/index.d.ts.map +1 -0
- package/dist/utils/validation/index.js +29 -0
- package/dist/utils/validation/index.js.map +1 -0
- package/dist/utils/validation/network.d.ts +19 -0
- package/dist/utils/validation/network.d.ts.map +1 -0
- package/dist/utils/validation/network.js +93 -0
- package/dist/utils/validation/network.js.map +1 -0
- package/dist/utils/validation/rateLimit.d.ts +21 -0
- package/dist/utils/validation/rateLimit.d.ts.map +1 -0
- package/dist/utils/validation/rateLimit.js +43 -0
- package/dist/utils/validation/rateLimit.js.map +1 -0
- package/dist/utils/validation/security.d.ts +29 -0
- package/dist/utils/validation/security.d.ts.map +1 -0
- package/dist/utils/validation/security.js +327 -0
- package/dist/utils/validation/security.js.map +1 -0
- package/dist/utils/validation/wordpress.d.ts +31 -0
- package/dist/utils/validation/wordpress.d.ts.map +1 -0
- package/dist/utils/validation/wordpress.js +146 -0
- package/dist/utils/validation/wordpress.js.map +1 -0
- package/dist/utils/validation.d.ts +13 -82
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +25 -343
- package/dist/utils/validation.js.map +1 -1
- package/docs/BADGE_UPDATES.md +132 -0
- package/docs/CI_CD_IMPROVEMENTS.md +191 -0
- package/docs/INCREMENTAL_COVERAGE.md +183 -0
- package/docs/api/README.md +3 -1
- package/docs/api/openapi.json +5 -1
- package/docs/api/summary.json +1 -1
- package/docs/api/tools/wp_create_post.md +12 -14
- package/docs/examples/claude-desktop-config.md +1 -1
- package/docs/examples/docker-production.md +100 -93
- package/docs/examples/multi-site-setup.md +5 -4
- package/docs/examples/single-site-setup.md +3 -4
- package/docs/examples/use-case-workflows.md +4 -5
- package/docs/integrations/claude-desktop.md +31 -31
- package/docs/integrations/cline.md +4 -4
- package/docs/integrations/vs-code.md +9 -8
- package/docs/user-guides/SMITHERY_SETUP.md +10 -10
- package/package.json +44 -25
- package/src/cache/CacheInvalidation.ts +12 -5
- package/src/cache/CacheManager.ts +18 -15
- package/src/cache/HttpCacheWrapper.ts +30 -59
- package/src/cache/__tests__/HttpCacheWrapper.test.ts +6 -5
- package/src/cache/index.ts +3 -14
- package/src/client/CachedWordPressClient.ts +32 -30
- package/src/client/MockWordPressClient.ts +4 -2
- package/src/client/api.ts +186 -64
- package/src/client/auth.ts +15 -40
- package/src/client/managers/AuthenticationManager.ts +337 -77
- package/src/client/managers/BaseManager.ts +18 -30
- package/src/client/managers/RequestManager.ts +39 -44
- package/src/config/Config.ts +308 -0
- package/src/config/ConfigurationSchema.ts +23 -2
- package/src/config/ServerConfiguration.ts +51 -47
- package/src/docs/DocumentationGenerator.ts +50 -39
- package/src/docs/MarkdownFormatter.ts +19 -29
- package/src/dxt-entry.cjs +26 -16
- package/src/dxt-entry.ts +17 -27
- package/src/index.ts +42 -28
- package/src/performance/MetricsCollector.ts +108 -86
- package/src/performance/PerformanceAnalytics.ts +69 -164
- package/src/performance/PerformanceMonitor.ts +32 -47
- package/src/performance/index.ts +2 -10
- package/src/security/AISecurityScanner.ts +22 -12
- package/src/security/AutomatedRemediation.ts +49 -18
- package/src/security/InputValidator.ts +9 -6
- package/src/security/SecurityCIPipeline.ts +53 -37
- package/src/security/SecurityConfig.ts +22 -22
- package/src/security/SecurityConfigManager.ts +23 -19
- package/src/security/SecurityMonitoring.ts +24 -21
- package/src/security/SecurityReviewer.ts +10 -7
- package/src/security/index.ts +64 -29
- package/src/server/ConnectionTester.ts +120 -31
- package/src/server/ToolRegistry.ts +31 -21
- package/src/tools/BaseToolManager.ts +286 -33
- package/src/tools/auth.ts +20 -8
- package/src/tools/cache.ts +5 -15
- package/src/tools/comments.ts +34 -48
- package/src/tools/media.ts +41 -53
- package/src/tools/pages.ts +32 -54
- package/src/tools/performance.ts +141 -176
- package/src/tools/posts/PostHandlers.ts +474 -0
- package/src/tools/posts/PostToolDefinitions.ts +250 -0
- package/src/tools/posts/index.ts +192 -0
- package/src/tools/posts.ts +24 -780
- package/src/tools/site.ts +34 -19
- package/src/tools/taxonomies.ts +41 -57
- package/src/tools/users.ts +28 -16
- package/src/types/client.ts +114 -138
- package/src/types/enhanced.ts +318 -0
- package/src/types/index.ts +51 -30
- package/src/types/mcp.ts +20 -42
- package/src/types/requests.ts +378 -0
- package/src/types/tools.ts +608 -0
- package/src/types/wordpress.ts +56 -34
- package/src/utils/debug.ts +77 -59
- package/src/utils/enhancedError.ts +8 -8
- package/src/utils/error.ts +53 -31
- package/src/utils/logger.ts +351 -0
- package/src/utils/streaming.ts +86 -68
- package/src/utils/toolWrapper.ts +10 -12
- package/src/utils/validation/core.ts +108 -0
- package/src/utils/validation/index.ts +36 -0
- package/src/utils/validation/network.ts +132 -0
- package/src/utils/validation/rateLimit.ts +54 -0
- package/src/utils/validation/security.ts +361 -0
- package/src/utils/validation/wordpress.ts +180 -0
- package/src/utils/validation.ts +47 -470
|
@@ -18,58 +18,46 @@ export abstract class BaseManager {
|
|
|
18
18
|
/**
|
|
19
19
|
* Standardized error handling for all managers
|
|
20
20
|
*/
|
|
21
|
-
protected handleError(error:
|
|
22
|
-
logError(`${operation} failed:`, error);
|
|
21
|
+
protected handleError(error: unknown, operation: string): never {
|
|
22
|
+
logError(`${operation} failed:`, error as Record<string, unknown>);
|
|
23
23
|
|
|
24
24
|
if (error instanceof WordPressAPIError) {
|
|
25
25
|
throw error;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
// Type guard for error-like objects
|
|
29
|
+
const isErrorLike = (err: unknown): err is { name?: string; code?: string; message?: string } => {
|
|
30
|
+
return typeof err === "object" && err !== null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
if (isErrorLike(error)) {
|
|
34
|
+
if (error.name === "AbortError" || error.code === "ABORT_ERR") {
|
|
35
|
+
throw new WordPressAPIError(`Request timeout after ${this.config.timeout}ms`, 408, "timeout");
|
|
36
|
+
}
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
503,
|
|
40
|
-
"connection_failed",
|
|
41
|
-
);
|
|
38
|
+
if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND") {
|
|
39
|
+
throw new WordPressAPIError(`Cannot connect to WordPress site: ${this.config.baseUrl}`, 503, "connection_failed");
|
|
40
|
+
}
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
const message = getErrorMessage(error);
|
|
45
|
-
throw new WordPressAPIError(
|
|
46
|
-
`${operation} failed: ${message}`,
|
|
47
|
-
500,
|
|
48
|
-
"unknown_error",
|
|
49
|
-
);
|
|
44
|
+
throw new WordPressAPIError(`${operation} failed: ${message}`, 500, "unknown_error");
|
|
50
45
|
}
|
|
51
46
|
|
|
52
47
|
/**
|
|
53
48
|
* Standardized success logging
|
|
54
49
|
*/
|
|
55
|
-
protected logSuccess(operation: string, details?:
|
|
50
|
+
protected logSuccess(operation: string, details?: unknown): void {
|
|
56
51
|
debug.log(`${operation} completed successfully`, details);
|
|
57
52
|
}
|
|
58
53
|
|
|
59
54
|
/**
|
|
60
55
|
* Validate required parameters
|
|
61
56
|
*/
|
|
62
|
-
protected validateRequired(
|
|
63
|
-
params: Record<string, any>,
|
|
64
|
-
requiredFields: string[],
|
|
65
|
-
): void {
|
|
57
|
+
protected validateRequired(params: Record<string, unknown>, requiredFields: string[]): void {
|
|
66
58
|
for (const field of requiredFields) {
|
|
67
59
|
if (params[field] === undefined || params[field] === null) {
|
|
68
|
-
throw new WordPressAPIError(
|
|
69
|
-
`Missing required parameter: ${field}`,
|
|
70
|
-
400,
|
|
71
|
-
"missing_parameter",
|
|
72
|
-
);
|
|
60
|
+
throw new WordPressAPIError(`Missing required parameter: ${field}`, 400, "missing_parameter");
|
|
73
61
|
}
|
|
74
62
|
}
|
|
75
63
|
}
|
|
@@ -4,13 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// Use native fetch in Node.js 18+
|
|
7
|
-
import type {
|
|
8
|
-
HTTPMethod,
|
|
9
|
-
RequestOptions,
|
|
10
|
-
ClientStats,
|
|
11
|
-
WordPressClientConfig,
|
|
12
|
-
} from "../../types/client.js";
|
|
7
|
+
import type { HTTPMethod, RequestOptions, ClientStats, WordPressClientConfig } from "../../types/client.js";
|
|
13
8
|
import { WordPressAPIError, RateLimitError } from "../../types/client.js";
|
|
9
|
+
import { config } from "../../config/Config.js";
|
|
14
10
|
import { BaseManager } from "./BaseManager.js";
|
|
15
11
|
import { AuthenticationManager } from "./AuthenticationManager.js";
|
|
16
12
|
import { debug, startTimer } from "../../utils/debug.js";
|
|
@@ -21,14 +17,11 @@ export class RequestManager extends BaseManager {
|
|
|
21
17
|
private requestInterval: number;
|
|
22
18
|
private authManager: AuthenticationManager;
|
|
23
19
|
|
|
24
|
-
constructor(
|
|
25
|
-
|
|
26
|
-
authManager: AuthenticationManager,
|
|
27
|
-
) {
|
|
28
|
-
super(config);
|
|
20
|
+
constructor(clientConfig: WordPressClientConfig, authManager: AuthenticationManager) {
|
|
21
|
+
super(clientConfig);
|
|
29
22
|
|
|
30
23
|
this.authManager = authManager;
|
|
31
|
-
this.requestInterval = 60000 /
|
|
24
|
+
this.requestInterval = 60000 / config().security.rateLimit;
|
|
32
25
|
this.stats = {
|
|
33
26
|
totalRequests: 0,
|
|
34
27
|
successfulRequests: 0,
|
|
@@ -42,23 +35,13 @@ export class RequestManager extends BaseManager {
|
|
|
42
35
|
/**
|
|
43
36
|
* Make HTTP request with retry logic and rate limiting
|
|
44
37
|
*/
|
|
45
|
-
async request<T>(
|
|
46
|
-
method: HTTPMethod,
|
|
47
|
-
endpoint: string,
|
|
48
|
-
data?: any,
|
|
49
|
-
options: RequestOptions = {},
|
|
50
|
-
): Promise<T> {
|
|
38
|
+
async request<T>(method: HTTPMethod, endpoint: string, data?: unknown, options: RequestOptions = {}): Promise<T> {
|
|
51
39
|
const timer = startTimer();
|
|
52
40
|
|
|
53
41
|
try {
|
|
54
42
|
await this.enforceRateLimit();
|
|
55
43
|
|
|
56
|
-
const response = await this.makeRequestWithRetry(
|
|
57
|
-
method,
|
|
58
|
-
endpoint,
|
|
59
|
-
data,
|
|
60
|
-
options,
|
|
61
|
-
);
|
|
44
|
+
const response = await this.makeRequestWithRetry(method, endpoint, data, options);
|
|
62
45
|
|
|
63
46
|
this.stats.successfulRequests++;
|
|
64
47
|
this.updateAverageResponseTime(timer.end());
|
|
@@ -78,27 +61,30 @@ export class RequestManager extends BaseManager {
|
|
|
78
61
|
private async makeRequestWithRetry<T>(
|
|
79
62
|
method: HTTPMethod,
|
|
80
63
|
endpoint: string,
|
|
81
|
-
data?:
|
|
64
|
+
data?: unknown,
|
|
82
65
|
options: RequestOptions = {},
|
|
83
66
|
): Promise<T> {
|
|
84
|
-
let lastError:
|
|
67
|
+
let lastError: unknown;
|
|
85
68
|
const maxRetries = options.retries ?? this.config.maxRetries ?? 3;
|
|
86
69
|
|
|
87
70
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
88
71
|
try {
|
|
89
72
|
return await this.makeRequest<T>(method, endpoint, data, options);
|
|
90
|
-
} catch (error:
|
|
73
|
+
} catch (error: unknown) {
|
|
91
74
|
lastError = error;
|
|
92
75
|
|
|
76
|
+
// Type guard for error-like objects
|
|
77
|
+
const isErrorLike = (err: unknown): err is { statusCode?: number; message?: string } => {
|
|
78
|
+
return typeof err === "object" && err !== null;
|
|
79
|
+
};
|
|
80
|
+
|
|
93
81
|
// Don't retry on authentication errors or client errors
|
|
94
|
-
if (error.statusCode < 500 || attempt === maxRetries) {
|
|
82
|
+
if ((isErrorLike(error) && error.statusCode && error.statusCode < 500) || attempt === maxRetries) {
|
|
95
83
|
throw error;
|
|
96
84
|
}
|
|
97
85
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
error.message,
|
|
101
|
-
);
|
|
86
|
+
const errorMessage = isErrorLike(error) && error.message ? error.message : String(error);
|
|
87
|
+
debug.log(`Request failed (attempt ${attempt}/${maxRetries}):`, errorMessage);
|
|
102
88
|
|
|
103
89
|
// Exponential backoff
|
|
104
90
|
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
|
|
@@ -115,7 +101,7 @@ export class RequestManager extends BaseManager {
|
|
|
115
101
|
private async makeRequest<T>(
|
|
116
102
|
method: HTTPMethod,
|
|
117
103
|
endpoint: string,
|
|
118
|
-
data?:
|
|
104
|
+
data?: unknown,
|
|
119
105
|
options: RequestOptions = {},
|
|
120
106
|
): Promise<T> {
|
|
121
107
|
const url = this.buildUrl(endpoint);
|
|
@@ -128,7 +114,7 @@ export class RequestManager extends BaseManager {
|
|
|
128
114
|
// Get authentication headers
|
|
129
115
|
const authHeaders = await this.authManager.getAuthHeaders();
|
|
130
116
|
|
|
131
|
-
const fetchOptions:
|
|
117
|
+
const fetchOptions: RequestInit = {
|
|
132
118
|
method,
|
|
133
119
|
headers: {
|
|
134
120
|
"Content-Type": "application/json",
|
|
@@ -142,11 +128,17 @@ export class RequestManager extends BaseManager {
|
|
|
142
128
|
if (data && method !== "GET") {
|
|
143
129
|
if (
|
|
144
130
|
data instanceof FormData ||
|
|
145
|
-
(data && typeof data.append === "function")
|
|
131
|
+
(typeof data === "object" && data && "append" in data && typeof data.append === "function")
|
|
146
132
|
) {
|
|
147
133
|
// For FormData, don't set Content-Type (let fetch set it with boundary)
|
|
148
|
-
|
|
149
|
-
|
|
134
|
+
if (
|
|
135
|
+
fetchOptions.headers &&
|
|
136
|
+
typeof fetchOptions.headers === "object" &&
|
|
137
|
+
"Content-Type" in fetchOptions.headers
|
|
138
|
+
) {
|
|
139
|
+
delete (fetchOptions.headers as Record<string, string>)["Content-Type"];
|
|
140
|
+
}
|
|
141
|
+
fetchOptions.body = data as FormData;
|
|
150
142
|
} else if (Buffer.isBuffer(data)) {
|
|
151
143
|
// For Buffer data, keep Content-Type from headers
|
|
152
144
|
fetchOptions.body = data;
|
|
@@ -175,18 +167,22 @@ export class RequestManager extends BaseManager {
|
|
|
175
167
|
/**
|
|
176
168
|
* Handle HTTP error responses
|
|
177
169
|
*/
|
|
178
|
-
private async handleErrorResponse(response:
|
|
179
|
-
let errorData:
|
|
170
|
+
private async handleErrorResponse(response: Response): Promise<never> {
|
|
171
|
+
let errorData: Record<string, unknown> = {};
|
|
180
172
|
|
|
181
173
|
try {
|
|
182
|
-
|
|
174
|
+
const jsonData = await response.json();
|
|
175
|
+
if (typeof jsonData === "object" && jsonData !== null) {
|
|
176
|
+
errorData = jsonData as Record<string, unknown>;
|
|
177
|
+
}
|
|
183
178
|
} catch {
|
|
184
179
|
// Ignore JSON parsing errors
|
|
185
180
|
}
|
|
186
181
|
|
|
187
182
|
const message =
|
|
188
|
-
errorData.message
|
|
189
|
-
|
|
183
|
+
(typeof errorData.message === "string" ? errorData.message : undefined) ||
|
|
184
|
+
`HTTP ${response.status}: ${response.statusText}`;
|
|
185
|
+
const code = (typeof errorData.code === "string" ? errorData.code : undefined) || "http_error";
|
|
190
186
|
|
|
191
187
|
if (response.status === 429) {
|
|
192
188
|
this.stats.rateLimitHits++;
|
|
@@ -233,8 +229,7 @@ export class RequestManager extends BaseManager {
|
|
|
233
229
|
const totalRequests = this.stats.successfulRequests;
|
|
234
230
|
const currentAverage = this.stats.averageResponseTime;
|
|
235
231
|
|
|
236
|
-
this.stats.averageResponseTime =
|
|
237
|
-
(currentAverage * (totalRequests - 1) + responseTime) / totalRequests;
|
|
232
|
+
this.stats.averageResponseTime = (currentAverage * (totalRequests - 1) + responseTime) / totalRequests;
|
|
238
233
|
}
|
|
239
234
|
|
|
240
235
|
/**
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Configuration Management
|
|
3
|
+
*
|
|
4
|
+
* Provides typed access to all environment variables and configuration options.
|
|
5
|
+
* Centralizes configuration logic and eliminates scattered process.env access.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface AppConfig {
|
|
9
|
+
// WordPress Connection
|
|
10
|
+
readonly wordpress: {
|
|
11
|
+
readonly siteUrl: string | undefined;
|
|
12
|
+
readonly username: string | undefined;
|
|
13
|
+
readonly appPassword: string | undefined;
|
|
14
|
+
readonly password: string | undefined;
|
|
15
|
+
readonly authMethod: string;
|
|
16
|
+
readonly timeout: number;
|
|
17
|
+
readonly maxRetries: number;
|
|
18
|
+
readonly jwtSecret: string | undefined;
|
|
19
|
+
readonly apiKey: string | undefined;
|
|
20
|
+
readonly cookieNonce: string | undefined;
|
|
21
|
+
readonly jwtPassword: string | undefined;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Application Environment
|
|
25
|
+
readonly app: {
|
|
26
|
+
readonly nodeEnv: string;
|
|
27
|
+
readonly isDevelopment: boolean;
|
|
28
|
+
readonly isProduction: boolean;
|
|
29
|
+
readonly isTest: boolean;
|
|
30
|
+
readonly isDXT: boolean;
|
|
31
|
+
readonly isCI: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Debug & Logging
|
|
35
|
+
readonly debug: {
|
|
36
|
+
readonly enabled: boolean;
|
|
37
|
+
readonly logLevel: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Caching
|
|
41
|
+
readonly cache: {
|
|
42
|
+
readonly disabled: boolean;
|
|
43
|
+
readonly ttl: number;
|
|
44
|
+
readonly maxItems: number;
|
|
45
|
+
readonly maxMemoryMB: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Security
|
|
49
|
+
readonly security: {
|
|
50
|
+
readonly rateLimitEnabled: boolean;
|
|
51
|
+
readonly rateLimitRequests: number;
|
|
52
|
+
readonly rateLimitWindow: number;
|
|
53
|
+
readonly rateLimit: number;
|
|
54
|
+
readonly strictMode: boolean;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Error Handling & Logging
|
|
58
|
+
readonly error: {
|
|
59
|
+
readonly legacyLogsEnabled: boolean;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Testing & Coverage
|
|
63
|
+
readonly testing: {
|
|
64
|
+
readonly coverageTolerance: number;
|
|
65
|
+
readonly skipPactTests: boolean;
|
|
66
|
+
readonly performanceTest: boolean;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// CI/CD Environment Detection
|
|
70
|
+
readonly ci: {
|
|
71
|
+
readonly isCI: boolean;
|
|
72
|
+
readonly provider: string | null;
|
|
73
|
+
readonly isGitHubActions: boolean;
|
|
74
|
+
readonly isTravis: boolean;
|
|
75
|
+
readonly isCircleCI: boolean;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Centralized Configuration Class
|
|
81
|
+
*
|
|
82
|
+
* Singleton that provides type-safe access to all configuration values.
|
|
83
|
+
* Replaces scattered process.env access throughout the codebase.
|
|
84
|
+
*/
|
|
85
|
+
export class Config {
|
|
86
|
+
private static instance: Config | null = null;
|
|
87
|
+
private readonly config: AppConfig;
|
|
88
|
+
|
|
89
|
+
private constructor() {
|
|
90
|
+
this.config = this.loadConfiguration();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get singleton instance
|
|
95
|
+
*/
|
|
96
|
+
public static getInstance(): Config {
|
|
97
|
+
if (!Config.instance) {
|
|
98
|
+
Config.instance = new Config();
|
|
99
|
+
}
|
|
100
|
+
return Config.instance;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get configuration object (read-only)
|
|
105
|
+
*/
|
|
106
|
+
public get(): AppConfig {
|
|
107
|
+
return this.config;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Load and validate all configuration from environment
|
|
112
|
+
*/
|
|
113
|
+
private loadConfiguration(): AppConfig {
|
|
114
|
+
const nodeEnv = process.env.NODE_ENV || "development";
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
wordpress: {
|
|
118
|
+
siteUrl: process.env.WORDPRESS_SITE_URL,
|
|
119
|
+
username: process.env.WORDPRESS_USERNAME,
|
|
120
|
+
appPassword: process.env.WORDPRESS_APP_PASSWORD,
|
|
121
|
+
password: process.env.WORDPRESS_PASSWORD,
|
|
122
|
+
authMethod: process.env.WORDPRESS_AUTH_METHOD || "app-password",
|
|
123
|
+
timeout: parseInt(process.env.WORDPRESS_TIMEOUT || "30000", 10),
|
|
124
|
+
maxRetries: parseInt(process.env.WORDPRESS_MAX_RETRIES || "3", 10),
|
|
125
|
+
jwtSecret: process.env.WORDPRESS_JWT_SECRET,
|
|
126
|
+
apiKey: process.env.WORDPRESS_API_KEY,
|
|
127
|
+
cookieNonce: process.env.WORDPRESS_COOKIE_NONCE,
|
|
128
|
+
jwtPassword: process.env.WORDPRESS_JWT_PASSWORD,
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
app: {
|
|
132
|
+
nodeEnv,
|
|
133
|
+
isDevelopment: nodeEnv === "development",
|
|
134
|
+
isProduction: nodeEnv === "production",
|
|
135
|
+
isTest: nodeEnv === "test",
|
|
136
|
+
isDXT: nodeEnv === "dxt",
|
|
137
|
+
isCI: this.detectCIEnvironment(),
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
debug: {
|
|
141
|
+
enabled: process.env.DEBUG === "true",
|
|
142
|
+
logLevel: process.env.LOG_LEVEL || "info",
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
cache: {
|
|
146
|
+
disabled: this.isTruthy(process.env.CACHE_DISABLED) || this.isTruthy(process.env.DISABLE_CACHE),
|
|
147
|
+
ttl: parseInt(process.env.CACHE_TTL || "300", 10), // 5 minutes default
|
|
148
|
+
maxItems: parseInt(process.env.CACHE_MAX_ITEMS || "1000", 10),
|
|
149
|
+
maxMemoryMB: parseInt(process.env.CACHE_MAX_MEMORY_MB || "50", 10),
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
security: {
|
|
153
|
+
rateLimitEnabled: process.env.RATE_LIMIT_ENABLED !== "false",
|
|
154
|
+
rateLimitRequests: parseInt(process.env.RATE_LIMIT_REQUESTS || "100", 10),
|
|
155
|
+
rateLimitWindow: parseInt(process.env.RATE_LIMIT_WINDOW || "60000", 10), // 1 minute
|
|
156
|
+
rateLimit: parseInt(process.env.RATE_LIMIT || "60", 10),
|
|
157
|
+
strictMode: process.env.SECURITY_STRICT_MODE === "true",
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
error: {
|
|
161
|
+
legacyLogsEnabled: process.env.LEGACY_ERROR_LOGS !== "0",
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
testing: {
|
|
165
|
+
coverageTolerance: parseFloat(process.env.COVERAGE_TOLERANCE || "1.0"),
|
|
166
|
+
skipPactTests: process.env.SKIP_PACT_TESTS === "true",
|
|
167
|
+
performanceTest: process.env.PERFORMANCE_TEST === "true",
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
ci: {
|
|
171
|
+
isCI: this.detectCIEnvironment(),
|
|
172
|
+
provider: this.detectCIProvider(),
|
|
173
|
+
isGitHubActions: process.env.GITHUB_ACTIONS === "true",
|
|
174
|
+
isTravis: process.env.TRAVIS === "true",
|
|
175
|
+
isCircleCI: process.env.CIRCLECI === "true",
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Check if a string value should be considered truthy
|
|
182
|
+
* Accepts common truthy string representations
|
|
183
|
+
*/
|
|
184
|
+
private isTruthy(value: string | undefined): boolean {
|
|
185
|
+
if (!value) return false;
|
|
186
|
+
const lowerValue = value.trim().toLowerCase();
|
|
187
|
+
return ["true", "1", "yes", "on", "enable", "enabled"].includes(lowerValue);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Detect if running in CI environment
|
|
192
|
+
*/
|
|
193
|
+
private detectCIEnvironment(): boolean {
|
|
194
|
+
return (
|
|
195
|
+
process.env.CI === "true" ||
|
|
196
|
+
process.env.NODE_ENV === "ci" ||
|
|
197
|
+
process.env.GITHUB_ACTIONS === "true" ||
|
|
198
|
+
process.env.TRAVIS === "true" ||
|
|
199
|
+
process.env.CIRCLECI === "true"
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Detect CI provider
|
|
205
|
+
*/
|
|
206
|
+
private detectCIProvider(): string | null {
|
|
207
|
+
if (process.env.GITHUB_ACTIONS === "true") return "github-actions";
|
|
208
|
+
if (process.env.TRAVIS === "true") return "travis";
|
|
209
|
+
if (process.env.CIRCLECI === "true") return "circleci";
|
|
210
|
+
if (process.env.CI === "true") return "generic";
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Convenience methods for common checks
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Should log debug information?
|
|
218
|
+
*/
|
|
219
|
+
public shouldDebug(): boolean {
|
|
220
|
+
return this.config.debug.enabled && !this.config.app.isDXT;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Should use caching?
|
|
225
|
+
*/
|
|
226
|
+
public shouldUseCache(): boolean {
|
|
227
|
+
return !this.config.cache.disabled;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Should log info messages? (not in test or DXT mode)
|
|
232
|
+
*/
|
|
233
|
+
public shouldLogInfo(): boolean {
|
|
234
|
+
return !this.config.app.isTest && !this.config.app.isDXT;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Is WordPress configuration complete?
|
|
239
|
+
*/
|
|
240
|
+
public hasWordPressConfig(): boolean {
|
|
241
|
+
const wp = this.config.wordpress;
|
|
242
|
+
return !!(wp.siteUrl && wp.username && wp.appPassword);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get environment-appropriate timeout for operations
|
|
247
|
+
*/
|
|
248
|
+
public getOperationTimeout(): number {
|
|
249
|
+
if (this.config.app.isTest) return 5000; // 5 seconds in tests
|
|
250
|
+
if (this.config.app.isCI) return 30000; // 30 seconds in CI
|
|
251
|
+
return 60000; // 1 minute in development/production
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Reset singleton (for testing)
|
|
256
|
+
*/
|
|
257
|
+
public static reset(): void {
|
|
258
|
+
Config.instance = null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Global configuration instance getter
|
|
264
|
+
* Use this throughout the application instead of process.env
|
|
265
|
+
*/
|
|
266
|
+
export const config = () => Config.getInstance().get();
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Configuration helper utilities
|
|
270
|
+
*/
|
|
271
|
+
export const ConfigHelpers = {
|
|
272
|
+
/**
|
|
273
|
+
* Get config instance
|
|
274
|
+
*/
|
|
275
|
+
get: () => Config.getInstance(),
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Quick environment checks
|
|
279
|
+
*/
|
|
280
|
+
isDev: () => config().app.isDevelopment,
|
|
281
|
+
isProd: () => config().app.isProduction,
|
|
282
|
+
isTest: () => config().app.isTest,
|
|
283
|
+
isDXT: () => config().app.isDXT,
|
|
284
|
+
isCI: () => config().app.isCI,
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Quick feature flags
|
|
288
|
+
*/
|
|
289
|
+
shouldDebug: () => Config.getInstance().shouldDebug(),
|
|
290
|
+
shouldUseCache: () => Config.getInstance().shouldUseCache(),
|
|
291
|
+
shouldLogInfo: () => Config.getInstance().shouldLogInfo(),
|
|
292
|
+
hasWordPressConfig: () => Config.getInstance().hasWordPressConfig(),
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Get timeout for different operation types
|
|
296
|
+
*/
|
|
297
|
+
getTimeout: (type: "operation" | "upload" | "test" = "operation") => {
|
|
298
|
+
const instance = Config.getInstance();
|
|
299
|
+
switch (type) {
|
|
300
|
+
case "upload":
|
|
301
|
+
return instance.getOperationTimeout() * 5; // 5x longer for uploads
|
|
302
|
+
case "test":
|
|
303
|
+
return config().app.isCI ? 30000 : 10000;
|
|
304
|
+
default:
|
|
305
|
+
return instance.getOperationTimeout();
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
};
|
|
@@ -6,7 +6,7 @@ import { z } from "zod";
|
|
|
6
6
|
const AuthMethodSchema = z.enum(["app-password", "jwt", "basic", "api-key", "cookie"] as const);
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Zod schema for URL validation
|
|
9
|
+
* Zod schema for URL validation with security checks
|
|
10
10
|
*/
|
|
11
11
|
const UrlSchema = z
|
|
12
12
|
.string()
|
|
@@ -14,7 +14,28 @@ const UrlSchema = z
|
|
|
14
14
|
.refine((url) => {
|
|
15
15
|
const parsed = new URL(url);
|
|
16
16
|
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
17
|
-
}, "URL must use http or https protocol")
|
|
17
|
+
}, "URL must use http or https protocol")
|
|
18
|
+
.refine((url) => {
|
|
19
|
+
// Additional security checks
|
|
20
|
+
const parsed = new URL(url);
|
|
21
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
22
|
+
|
|
23
|
+
// In production, block localhost and private IPs
|
|
24
|
+
if (process.env.NODE_ENV === "production") {
|
|
25
|
+
if (
|
|
26
|
+
hostname === "localhost" ||
|
|
27
|
+
hostname === "127.0.0.1" ||
|
|
28
|
+
hostname === "::1" ||
|
|
29
|
+
hostname.match(/^10\./) ||
|
|
30
|
+
hostname.match(/^172\.(1[6-9]|2[0-9]|3[01])\./) ||
|
|
31
|
+
hostname.match(/^192\.168\./)
|
|
32
|
+
) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return true;
|
|
38
|
+
}, "Private/localhost URLs not allowed in production");
|
|
18
39
|
|
|
19
40
|
/**
|
|
20
41
|
* Zod schema for WordPress site configuration
|