@pixelated-tech/components 3.3.5 → 3.4.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.
Files changed (158) hide show
  1. package/README.COMPONENTS.md +126 -0
  2. package/README.md +15 -9
  3. package/dist/components/admin/componentusage/componentAnalysis.js +144 -0
  4. package/dist/components/admin/componentusage/componentDiscovery.js +85 -0
  5. package/dist/components/admin/deploy/deployment.integration.js +170 -0
  6. package/dist/components/admin/site-health/google-api-auth.js +69 -0
  7. package/dist/components/admin/site-health/seo-metrics.config.json +265 -0
  8. package/dist/components/admin/site-health/site-health-accessibility.js +158 -0
  9. package/dist/components/admin/site-health/site-health-axe-core.integration.js +119 -0
  10. package/dist/components/admin/site-health/site-health-axe-core.js +53 -0
  11. package/dist/components/admin/site-health/site-health-cache.js +23 -0
  12. package/dist/components/admin/site-health/site-health-core-web-vitals.integration.js +208 -0
  13. package/dist/components/admin/site-health/site-health-dependency-vulnerabilities.js +38 -0
  14. package/dist/components/admin/site-health/site-health-github.integration.js +81 -0
  15. package/dist/components/admin/site-health/site-health-github.js +34 -0
  16. package/dist/components/admin/site-health/site-health-google-analytics.integration.js +112 -0
  17. package/dist/components/admin/site-health/site-health-google-analytics.js +43 -0
  18. package/dist/components/admin/site-health/site-health-google-search-console.integration.js +118 -0
  19. package/dist/components/admin/site-health/site-health-google-search-console.js +43 -0
  20. package/dist/components/admin/site-health/site-health-indicators.js +71 -0
  21. package/dist/components/admin/site-health/site-health-on-site-seo.integration.js +578 -0
  22. package/dist/components/admin/site-health/site-health-on-site-seo.js +204 -0
  23. package/dist/components/admin/site-health/site-health-overview.js +65 -0
  24. package/dist/components/admin/site-health/site-health-performance.js +191 -0
  25. package/dist/components/admin/site-health/site-health-security.integration.js +109 -0
  26. package/dist/components/admin/site-health/site-health-security.js +169 -0
  27. package/dist/components/admin/site-health/site-health-seo.js +124 -0
  28. package/dist/components/admin/site-health/site-health-template.js +62 -0
  29. package/dist/components/admin/site-health/site-health-types.js +1 -0
  30. package/dist/components/admin/site-health/site-health-uptime.integration.js +29 -0
  31. package/dist/components/admin/site-health/site-health-uptime.js +30 -0
  32. package/dist/components/admin/site-health/site-health.css +427 -0
  33. package/dist/components/admin/sites/sites.integration.js +117 -0
  34. package/dist/components/cms/contentful.management.js +104 -0
  35. package/dist/components/cms/hubspot.components.js +3 -3
  36. package/dist/components/config/config.client.js +21 -10
  37. package/dist/components/general/table.js +3 -1
  38. package/dist/components/seo/googleanalytics.js +1 -2
  39. package/dist/components/shoppingcart/shipping.from.json +101 -0
  40. package/dist/components/shoppingcart/shipping.parcel.json +112 -0
  41. package/dist/components/shoppingcart/shipping.to.json +422 -0
  42. package/dist/components/shoppingcart/shoppingCartDiscountCodes.json +26 -0
  43. package/dist/components/shoppingcart/shoppingcart.components.js +1 -1
  44. package/dist/components/sitebuilder/config/ConfigBuilder.js +36 -140
  45. package/dist/components/sitebuilder/config/siteinfo-form.json +200 -0
  46. package/dist/components/sitebuilder/config/visualdesignform.json +244 -0
  47. package/dist/components/structured/buzzwordbingo.js +3 -2
  48. package/dist/data/404-data.json +128 -102
  49. package/dist/data/flickr.json +25 -0
  50. package/dist/data/form.json +368 -368
  51. package/dist/data/recipes.json +3251 -3251
  52. package/dist/data/references.json +138 -137
  53. package/dist/data/requestform.json +111 -0
  54. package/dist/data/requests.json +136 -135
  55. package/dist/data/resume.json +2573 -2575
  56. package/dist/data/routes.json +238 -238
  57. package/dist/data/routes2.json +141 -140
  58. package/dist/index.js +16 -3
  59. package/dist/index.server.js +36 -15
  60. package/dist/types/components/admin/componentusage/componentAnalysis.d.ts +35 -0
  61. package/dist/types/components/admin/componentusage/componentAnalysis.d.ts.map +1 -0
  62. package/dist/types/components/admin/componentusage/componentDiscovery.d.ts +10 -0
  63. package/dist/types/components/admin/componentusage/componentDiscovery.d.ts.map +1 -0
  64. package/dist/types/components/admin/deploy/deployment.integration.d.ts +26 -0
  65. package/dist/types/components/admin/deploy/deployment.integration.d.ts.map +1 -0
  66. package/dist/types/components/admin/site-health/google-api-auth.d.ts +37 -0
  67. package/dist/types/components/admin/site-health/google-api-auth.d.ts.map +1 -0
  68. package/dist/types/components/admin/site-health/site-health-accessibility.d.ts +6 -0
  69. package/dist/types/components/admin/site-health/site-health-accessibility.d.ts.map +1 -0
  70. package/dist/types/components/admin/site-health/site-health-axe-core.d.ts +6 -0
  71. package/dist/types/components/admin/site-health/site-health-axe-core.d.ts.map +1 -0
  72. package/dist/types/components/admin/site-health/site-health-axe-core.integration.d.ts +63 -0
  73. package/dist/types/components/admin/site-health/site-health-axe-core.integration.d.ts.map +1 -0
  74. package/dist/types/components/admin/site-health/site-health-cache.d.ts +12 -0
  75. package/dist/types/components/admin/site-health/site-health-cache.d.ts.map +1 -0
  76. package/dist/types/components/admin/site-health/site-health-core-web-vitals.integration.d.ts +3 -0
  77. package/dist/types/components/admin/site-health/site-health-core-web-vitals.integration.d.ts.map +1 -0
  78. package/dist/types/components/admin/site-health/site-health-dependency-vulnerabilities.d.ts +6 -0
  79. package/dist/types/components/admin/site-health/site-health-dependency-vulnerabilities.d.ts.map +1 -0
  80. package/dist/types/components/admin/site-health/site-health-github.d.ts +8 -0
  81. package/dist/types/components/admin/site-health/site-health-github.d.ts.map +1 -0
  82. package/dist/types/components/admin/site-health/site-health-github.integration.d.ts +26 -0
  83. package/dist/types/components/admin/site-health/site-health-github.integration.d.ts.map +1 -0
  84. package/dist/types/components/admin/site-health/site-health-google-analytics.d.ts +8 -0
  85. package/dist/types/components/admin/site-health/site-health-google-analytics.d.ts.map +1 -0
  86. package/dist/types/components/admin/site-health/site-health-google-analytics.integration.d.ts +26 -0
  87. package/dist/types/components/admin/site-health/site-health-google-analytics.integration.d.ts.map +1 -0
  88. package/dist/types/components/admin/site-health/site-health-google-search-console.d.ts +8 -0
  89. package/dist/types/components/admin/site-health/site-health-google-search-console.d.ts.map +1 -0
  90. package/dist/types/components/admin/site-health/site-health-google-search-console.integration.d.ts +46 -0
  91. package/dist/types/components/admin/site-health/site-health-google-search-console.integration.d.ts.map +1 -0
  92. package/dist/types/components/admin/site-health/site-health-indicators.d.ts +73 -0
  93. package/dist/types/components/admin/site-health/site-health-indicators.d.ts.map +1 -0
  94. package/dist/types/components/admin/site-health/site-health-on-site-seo.d.ts +4 -0
  95. package/dist/types/components/admin/site-health/site-health-on-site-seo.d.ts.map +1 -0
  96. package/dist/types/components/admin/site-health/site-health-on-site-seo.integration.d.ts +34 -0
  97. package/dist/types/components/admin/site-health/site-health-on-site-seo.integration.d.ts.map +1 -0
  98. package/dist/types/components/admin/site-health/site-health-overview.d.ts +6 -0
  99. package/dist/types/components/admin/site-health/site-health-overview.d.ts.map +1 -0
  100. package/dist/types/components/admin/site-health/site-health-performance.d.ts +6 -0
  101. package/dist/types/components/admin/site-health/site-health-performance.d.ts.map +1 -0
  102. package/dist/types/components/admin/site-health/site-health-security.d.ts +6 -0
  103. package/dist/types/components/admin/site-health/site-health-security.d.ts.map +1 -0
  104. package/dist/types/components/admin/site-health/site-health-security.integration.d.ts +29 -0
  105. package/dist/types/components/admin/site-health/site-health-security.integration.d.ts.map +1 -0
  106. package/dist/types/components/admin/site-health/site-health-seo.d.ts +6 -0
  107. package/dist/types/components/admin/site-health/site-health-seo.d.ts.map +1 -0
  108. package/dist/types/components/admin/site-health/site-health-template.d.ts +12 -0
  109. package/dist/types/components/admin/site-health/site-health-template.d.ts.map +1 -0
  110. package/dist/types/components/admin/site-health/site-health-types.d.ts +186 -0
  111. package/dist/types/components/admin/site-health/site-health-types.d.ts.map +1 -0
  112. package/dist/types/components/admin/site-health/site-health-uptime.d.ts +6 -0
  113. package/dist/types/components/admin/site-health/site-health-uptime.d.ts.map +1 -0
  114. package/dist/types/components/admin/site-health/site-health-uptime.integration.d.ts +10 -0
  115. package/dist/types/components/admin/site-health/site-health-uptime.integration.d.ts.map +1 -0
  116. package/dist/types/components/admin/sites/sites.integration.d.ts +40 -0
  117. package/dist/types/components/admin/sites/sites.integration.d.ts.map +1 -0
  118. package/dist/types/components/cms/contentful.management.d.ts +41 -0
  119. package/dist/types/components/cms/contentful.management.d.ts.map +1 -1
  120. package/dist/types/components/config/config.client.d.ts +1 -2
  121. package/dist/types/components/config/config.client.d.ts.map +1 -1
  122. package/dist/types/components/general/table.d.ts +1 -0
  123. package/dist/types/components/general/table.d.ts.map +1 -1
  124. package/dist/types/components/seo/googleanalytics.d.ts +1 -1
  125. package/dist/types/components/seo/googleanalytics.d.ts.map +1 -1
  126. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts +4 -4
  127. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -1
  128. package/dist/types/components/structured/buzzwordbingo.d.ts +1 -1
  129. package/dist/types/components/structured/buzzwordbingo.d.ts.map +1 -1
  130. package/dist/types/components/structured/buzzwordbingo.words.d.ts +2 -0
  131. package/dist/types/components/structured/buzzwordbingo.words.d.ts.map +1 -0
  132. package/dist/types/index.d.ts +16 -3
  133. package/dist/types/index.server.d.ts +36 -13
  134. package/dist/types/stories/admin/preview.d.ts +12 -0
  135. package/dist/types/stories/admin/preview.d.ts.map +1 -0
  136. package/dist/types/stories/admin/site-health.stories.d.ts +65 -0
  137. package/dist/types/stories/admin/site-health.stories.d.ts.map +1 -0
  138. package/dist/types/stories/structured/buzzword-bingo.stories.d.ts +1 -1
  139. package/dist/types/stories/structured/buzzword-bingo.stories.d.ts.map +1 -1
  140. package/dist/types/tests/site-health-axe-core.test.d.ts +2 -0
  141. package/dist/types/tests/site-health-axe-core.test.d.ts.map +1 -0
  142. package/dist/types/tests/site-health-cache.test.d.ts +2 -0
  143. package/dist/types/tests/site-health-cache.test.d.ts.map +1 -0
  144. package/dist/types/tests/site-health-indicators.test.d.ts +2 -0
  145. package/dist/types/tests/site-health-indicators.test.d.ts.map +1 -0
  146. package/dist/types/tests/site-health-overview.test.d.ts +2 -0
  147. package/dist/types/tests/site-health-overview.test.d.ts.map +1 -0
  148. package/dist/types/tests/site-health-template.test.d.ts +2 -0
  149. package/dist/types/tests/site-health-template.test.d.ts.map +1 -0
  150. package/dist/types/tests/sites.integration.test.d.ts +2 -0
  151. package/dist/types/tests/sites.integration.test.d.ts.map +1 -0
  152. package/package.json +15 -9
  153. package/dist/data/shipping.to.json +0 -422
  154. package/dist/data/siteinfo-form.json +0 -200
  155. package/dist/data/visualdesignform.json +0 -244
  156. package/dist/types/data/buzzwords.d.ts +0 -2
  157. package/dist/types/data/buzzwords.d.ts.map +0 -1
  158. /package/dist/{data/buzzwords.js → components/structured/buzzwordbingo.words.js} +0 -0
@@ -0,0 +1,208 @@
1
+ "use server";
2
+ const psiCache = new Map();
3
+ const CACHE_TTL_SUCCESS = 60 * 60 * 1000; // 1 hour for successful results
4
+ const CACHE_TTL_ERROR = 5 * 60 * 1000; // 5 minutes for error results
5
+ // Clean up expired cache entries periodically
6
+ setInterval(() => {
7
+ const now = Date.now();
8
+ for (const [key, entry] of psiCache.entries()) {
9
+ const ttl = entry.data.status === 'success' ? CACHE_TTL_SUCCESS : CACHE_TTL_ERROR;
10
+ if (now - entry.timestamp > ttl) {
11
+ psiCache.delete(key);
12
+ }
13
+ }
14
+ }, 10 * 60 * 1000); // Clean up every 10 minutes
15
+ export async function performCoreWebVitalsAnalysis(url, siteName, useCache = true) {
16
+ try {
17
+ // Check cache first (if caching is enabled)
18
+ const cacheKey = `${siteName}:${url}`;
19
+ if (useCache) {
20
+ const cached = psiCache.get(cacheKey);
21
+ if (cached) {
22
+ const ttl = cached.data.status === 'success' ? CACHE_TTL_SUCCESS : CACHE_TTL_ERROR;
23
+ if ((Date.now() - cached.timestamp) < ttl) {
24
+ return cached.data;
25
+ }
26
+ }
27
+ }
28
+ // Fetch PSI data
29
+ const psiData = await fetchPSIData(url);
30
+ // Process the PSI data
31
+ const resultData = processPSIData(psiData, siteName, url);
32
+ // Cache successful results (if caching is enabled)
33
+ if (useCache) {
34
+ psiCache.set(cacheKey, {
35
+ data: resultData,
36
+ timestamp: Date.now()
37
+ });
38
+ }
39
+ return resultData;
40
+ }
41
+ catch (error) {
42
+ console.warn(`PSI API failed for ${siteName}:`, error);
43
+ // Return error status instead of mock data
44
+ const errorResult = {
45
+ site: siteName,
46
+ url: url,
47
+ metrics: {
48
+ cls: 0,
49
+ fid: 0,
50
+ lcp: 0,
51
+ fcp: 0,
52
+ ttfb: 0,
53
+ speedIndex: 0,
54
+ interactive: 0,
55
+ totalBlockingTime: 0,
56
+ firstMeaningfulPaint: 0,
57
+ },
58
+ scores: {
59
+ performance: 0,
60
+ accessibility: 0,
61
+ 'best-practices': 0,
62
+ seo: 0,
63
+ pwa: 0,
64
+ },
65
+ categories: {
66
+ performance: { id: 'performance', title: 'Performance', score: null, audits: [] },
67
+ accessibility: { id: 'accessibility', title: 'Accessibility', score: null, audits: [] },
68
+ 'best-practices': { id: 'best-practices', title: 'Best Practices', score: null, audits: [] },
69
+ seo: { id: 'seo', title: 'SEO', score: null, audits: [] },
70
+ pwa: { id: 'pwa', title: 'PWA', score: null, audits: [] },
71
+ },
72
+ timestamp: new Date().toISOString(),
73
+ status: 'error',
74
+ error: error instanceof Error ? error.message : 'PSI API failed',
75
+ };
76
+ // Cache error results with shorter TTL (5 minutes) (if caching is enabled)
77
+ if (useCache) {
78
+ psiCache.set(`${siteName}:${url}`, {
79
+ data: errorResult,
80
+ timestamp: Date.now()
81
+ });
82
+ }
83
+ return errorResult;
84
+ }
85
+ }
86
+ async function fetchPSIData(url) {
87
+ const apiKey = process.env.GOOGLE_API_KEY;
88
+ if (!apiKey) {
89
+ throw new Error('GOOGLE_API_KEY environment variable is not set');
90
+ }
91
+ const psiUrl = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(url)}&key=${apiKey}&strategy=mobile&category=performance&category=accessibility&category=best-practices&category=seo&category=pwa`;
92
+ const fetchWithRetry = async (url, maxRetries = 2) => {
93
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
94
+ try {
95
+ const controller = new AbortController();
96
+ const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout
97
+ const response = await fetch(url, {
98
+ signal: controller.signal,
99
+ headers: {
100
+ 'User-Agent': 'Mozilla/5.0 (compatible; SiteHealthMonitor/1.0)'
101
+ }
102
+ });
103
+ clearTimeout(timeoutId);
104
+ return response;
105
+ }
106
+ catch (error) {
107
+ if (attempt === maxRetries || error instanceof Error && error.name === 'AbortError') {
108
+ const errorMessage = error instanceof Error && error.name === 'AbortError'
109
+ ? 'PSI API request timed out after 60 seconds'
110
+ : `PSI API request failed: ${error instanceof Error ? error.message : 'Unknown error'}`;
111
+ throw new Error(errorMessage);
112
+ }
113
+ // Wait before retry (exponential backoff)
114
+ await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
115
+ }
116
+ }
117
+ throw new Error('Max retries exceeded');
118
+ };
119
+ const psiResponse = await fetchWithRetry(psiUrl);
120
+ if (!psiResponse.ok) {
121
+ // Log the error response for debugging
122
+ const errorText = await psiResponse.text();
123
+ console.error(`PSI API error for ${url}:`, {
124
+ status: psiResponse.status,
125
+ statusText: psiResponse.statusText,
126
+ url: psiUrl,
127
+ response: errorText
128
+ });
129
+ throw new Error(`PSI API returned ${psiResponse.status}: ${psiResponse.statusText}`);
130
+ }
131
+ const psiData = await psiResponse.json();
132
+ // Check if we have valid data
133
+ if (psiData.lighthouseResult?.audits &&
134
+ Object.keys(psiData.lighthouseResult.audits).length > 0) {
135
+ return psiData;
136
+ }
137
+ else {
138
+ // PSI API returned data but no audits - likely rate limited or invalid response
139
+ throw new Error('Invalid PSI API response or rate limited');
140
+ }
141
+ }
142
+ function processPSIData(psiData, siteName, url) {
143
+ const audits = psiData.lighthouseResult.audits;
144
+ const categories = psiData.lighthouseResult.categories;
145
+ // Extract metrics with proper fallbacks
146
+ const realMetrics = {
147
+ cls: audits['cumulative-layout-shift']?.numericValue || audits['cumulative-layout-shift']?.displayValue ? parseFloat(audits['cumulative-layout-shift'].displayValue || audits['cumulative-layout-shift'].numericValue) : 0,
148
+ fid: audits['max-potential-fid']?.numericValue || audits['max-potential-fid']?.displayValue ? parseFloat(audits['max-potential-fid'].displayValue || audits['max-potential-fid'].numericValue) : 0,
149
+ lcp: audits['largest-contentful-paint']?.numericValue || audits['largest-contentful-paint']?.displayValue ? parseFloat(audits['largest-contentful-paint'].displayValue || audits['largest-contentful-paint'].numericValue) : 0,
150
+ fcp: audits['first-contentful-paint']?.numericValue || audits['first-contentful-paint']?.displayValue ? parseFloat(audits['first-contentful-paint'].displayValue || audits['first-contentful-paint'].numericValue) : 0,
151
+ ttfb: audits['server-response-time']?.numericValue ? audits['server-response-time'].numericValue * 1000 : audits['server-response-time']?.displayValue ? parseFloat(audits['server-response-time'].displayValue) * 1000 : 0,
152
+ speedIndex: audits['speed-index']?.numericValue || audits['speed-index']?.displayValue ? parseFloat(audits['speed-index'].displayValue || audits['speed-index'].numericValue) : 0,
153
+ interactive: audits['interactive']?.numericValue || audits['interactive']?.displayValue ? parseFloat(audits['interactive'].displayValue || audits['interactive'].numericValue) : 0,
154
+ totalBlockingTime: audits['total-blocking-time']?.numericValue || audits['total-blocking-time']?.displayValue ? parseFloat(audits['total-blocking-time'].displayValue || audits['total-blocking-time'].numericValue) : 0,
155
+ firstMeaningfulPaint: audits['first-meaningful-paint']?.numericValue || audits['first-meaningful-paint']?.displayValue ? parseFloat(audits['first-meaningful-paint'].displayValue || audits['first-meaningful-paint'].numericValue) : 0,
156
+ };
157
+ // Extract category scores
158
+ const scores = {
159
+ performance: categories?.performance?.score ?? null,
160
+ accessibility: categories?.accessibility?.score ?? null,
161
+ 'best-practices': categories?.['best-practices']?.score ?? null,
162
+ seo: categories?.seo?.score ?? null,
163
+ pwa: categories?.pwa?.score ?? null,
164
+ };
165
+ // Extract category details with audits
166
+ const categoryDetails = {
167
+ performance: extractCategoryData(categories?.performance, audits),
168
+ accessibility: extractCategoryData(categories?.accessibility, audits),
169
+ 'best-practices': extractCategoryData(categories?.['best-practices'], audits),
170
+ seo: extractCategoryData(categories?.seo, audits),
171
+ pwa: extractCategoryData(categories?.pwa, audits),
172
+ };
173
+ return {
174
+ site: siteName,
175
+ url: url,
176
+ metrics: realMetrics,
177
+ scores: scores,
178
+ categories: categoryDetails,
179
+ timestamp: new Date().toISOString(),
180
+ status: 'success',
181
+ };
182
+ }
183
+ function extractCategoryData(category, audits) {
184
+ if (!category) {
185
+ return { id: 'unknown', title: 'Unknown', score: null, audits: [] };
186
+ }
187
+ const categoryAudits = category.auditRefs?.map((ref) => {
188
+ const audit = audits[ref.id];
189
+ if (!audit)
190
+ return null;
191
+ return {
192
+ id: audit.id,
193
+ title: audit.title,
194
+ description: audit.description,
195
+ score: audit.score,
196
+ scoreDisplayMode: audit.scoreDisplayMode,
197
+ displayValue: audit.displayValue,
198
+ numericValue: audit.numericValue,
199
+ details: audit.details,
200
+ };
201
+ }).filter(Boolean) || [];
202
+ return {
203
+ id: category.id,
204
+ title: category.title,
205
+ score: category.score,
206
+ audits: categoryAudits,
207
+ };
208
+ }
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useCallback } from 'react';
4
+ import { SiteHealthTemplate } from './site-health-template';
5
+ export function SiteHealthDependencyVulnerabilities({ siteName }) {
6
+ const fetchDependencyData = useCallback(async (site) => {
7
+ const response = await fetch(`/api/site-health/security?siteName=${encodeURIComponent(site)}`);
8
+ const result = await response.json();
9
+ if (!result.success) {
10
+ throw new Error(result.error || 'Failed to fetch dependency data');
11
+ }
12
+ return result;
13
+ }, []);
14
+ return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "Dependency Vulnerability", fetchData: fetchDependencyData, children: (data) => {
15
+ if (!data) {
16
+ return (_jsx("p", { style: { color: '#6b7280' }, children: "No dependency data available for this site." }));
17
+ }
18
+ return (_jsxs(_Fragment, { children: [_jsx("h4", { className: "health-site-name", children: siteName.replace('-', ' ') }), _jsxs("p", { className: "health-site-url", children: ["URL: ", data.url] }), _jsx("div", { className: "health-score-container", children: _jsxs("div", { className: "health-score-item", children: [_jsx("div", { className: "health-score-label", children: "Overall Status" }), _jsx("div", { className: "health-score-value", style: {
19
+ color: data.status === 'Secure' ? '#10b981' :
20
+ data.status === 'Low Risk' ? '#f59e0b' :
21
+ data.status === 'Moderate Risk' ? '#f59e0b' :
22
+ data.status === 'High Risk' ? '#ef4444' :
23
+ data.status === 'Critical' ? '#ef4444' :
24
+ '#6b7280'
25
+ }, children: data.status }), _jsx("div", { className: "health-score-bar", children: _jsx("div", { className: "health-score-fill", style: {
26
+ width: data.status === 'Secure' ? '100%' :
27
+ data.status === 'Low Risk' ? '75%' :
28
+ data.status === 'Moderate Risk' ? '50%' :
29
+ data.status === 'High Risk' ? '25%' :
30
+ data.status === 'Critical' ? '10%' : '0%',
31
+ backgroundColor: data.status === 'Secure' ? '#10b981' :
32
+ data.status === 'Low Risk' ? '#f59e0b' :
33
+ data.status === 'Moderate Risk' ? '#f59e0b' :
34
+ data.status === 'High Risk' ? '#ef4444' :
35
+ data.status === 'Critical' ? '#ef4444' : '#6b7280'
36
+ } }) })] }) }), _jsxs("div", { className: "health-audit-item", children: [_jsx("span", { className: "health-audit-icon", style: { color: '#10b981' }, children: "\u2713" }), _jsx("div", { className: "health-audit-content", children: _jsxs("span", { className: "health-audit-title", children: ["Dependencies: ", data.totalDependencies || data.dependencies || 0] }) })] }), data.vulnerabilities && data.vulnerabilities.length > 0 && (_jsxs("div", { children: [_jsxs("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: ["Vulnerabilities (", data.summary.total, ")"] }), _jsx("div", { className: "space-y-2", children: data.vulnerabilities.map((vuln, index) => (_jsx("div", { className: `health-vulnerability-item health-vulnerability-${vuln.severity}`, children: _jsxs("div", { className: "health-vulnerability-header", children: [_jsx("span", { className: "health-vulnerability-severity", children: vuln.severity }), _jsxs("div", { children: [_jsx("span", { className: "health-vulnerability-name", children: vuln.name }), vuln.title && (_jsx("p", { className: "health-vulnerability-details", children: vuln.title })), _jsxs("div", { className: "health-vulnerability-meta", children: [_jsxs("span", { className: "health-vulnerability-range", children: ["Range: ", vuln.range] }), vuln.fixAvailable && (_jsx("span", { className: "health-vulnerability-fix", children: "\u2713 Fix available" }))] }), vuln.url && (_jsx("a", { href: vuln.url, target: "_blank", rel: "noopener noreferrer", className: "health-vulnerability-link", children: "View details \u2192" }))] })] }) }, index))) })] })), (!data.vulnerabilities || data.vulnerabilities.length === 0) && data.status === 'Secure' && (_jsxs("div", { className: "health-audit-item", children: [_jsx("span", { className: "health-audit-icon", style: { color: '#10b981' }, children: "\u2713" }), _jsx("div", { className: "health-audit-content", children: _jsx("span", { className: "health-audit-title", children: "No vulnerabilities found" }) })] })), _jsxs("p", { className: "health-timestamp", children: ["Last checked: ", new Date(data.timestamp).toLocaleString()] })] }));
37
+ } }));
38
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Git Health Integration Services
3
+ * Server-side utilities for analyzing git repository health
4
+ */
5
+ import { exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ const execAsync = promisify(exec);
10
+ /**
11
+ * Analyze git repository health for a site
12
+ */
13
+ export async function analyzeGitHealth(siteConfig, startDate, endDate) {
14
+ try {
15
+ const { localPath } = siteConfig;
16
+ // Check if the local path exists and is a git repository
17
+ if (!fs.existsSync(localPath)) {
18
+ throw new Error('Site directory not found');
19
+ }
20
+ const gitDir = path.join(localPath, '.git');
21
+ if (!fs.existsSync(gitDir)) {
22
+ throw new Error('Not a git repository');
23
+ }
24
+ // Build git log command with date range
25
+ let sinceOption = '--since="30 days ago"';
26
+ if (startDate && endDate) {
27
+ sinceOption = `--since="${startDate}" --before="${endDate}"`;
28
+ }
29
+ else if (startDate) {
30
+ sinceOption = `--since="${startDate}"`;
31
+ }
32
+ else if (endDate) {
33
+ sinceOption = `--before="${endDate}"`;
34
+ }
35
+ // Get git log
36
+ const gitCommand = `git log --oneline ${sinceOption} --pretty=format:"%H|%ad|%s|%an" --date=iso`;
37
+ const { stdout: logOutput } = await execAsync(gitCommand, { cwd: localPath });
38
+ const commits = logOutput
39
+ .trim()
40
+ .split('\n')
41
+ .filter(line => line.trim())
42
+ .map(line => {
43
+ const [hash, date, ...messageParts] = line.split('|');
44
+ const message = messageParts.slice(0, -1).join('|');
45
+ const author = messageParts[messageParts.length - 1];
46
+ return {
47
+ hash,
48
+ date,
49
+ message,
50
+ author
51
+ };
52
+ })
53
+ .filter(commit => !/^\d+\.\d+\.\d+$/.test(commit.message.trim())) // Filter out version-only commits
54
+ .slice(0, (startDate && endDate) ? 100 : 20); // Limit to more commits when date range is specified
55
+ // Try to associate commits with versions
56
+ for (const commit of commits) {
57
+ try {
58
+ const { stdout: tagOutput } = await execAsync(`git describe --tags --contains ${commit.hash} 2>/dev/null || echo ""`, { cwd: localPath });
59
+ if (tagOutput.trim()) {
60
+ commit.version = tagOutput.trim();
61
+ }
62
+ }
63
+ catch {
64
+ // Ignore errors for commits not associated with tags
65
+ }
66
+ }
67
+ return {
68
+ commits,
69
+ timestamp: new Date().toISOString(),
70
+ status: 'success'
71
+ };
72
+ }
73
+ catch (error) {
74
+ return {
75
+ commits: [],
76
+ timestamp: new Date().toISOString(),
77
+ status: 'error',
78
+ error: error instanceof Error ? error.message : 'Failed to analyze git health'
79
+ };
80
+ }
81
+ }
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Table } from '@pixelated-tech/components';
4
+ import { SiteHealthTemplate } from './site-health-template';
5
+ export function SiteHealthGit({ siteName, startDate, endDate }) {
6
+ const fetchGitData = async (site) => {
7
+ const params = new URLSearchParams({ site: encodeURIComponent(site) });
8
+ if (startDate)
9
+ params.append('startDate', startDate);
10
+ if (endDate)
11
+ params.append('endDate', endDate);
12
+ const response = await fetch(`/api/site-health/git?${params.toString()}`);
13
+ if (!response.ok) {
14
+ throw new Error('Failed to fetch git data');
15
+ }
16
+ const data = await response.json();
17
+ return data;
18
+ };
19
+ return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "Git Push Notes", fetchData: fetchGitData, children: (data) => {
20
+ if (!data || !data.success) {
21
+ return (_jsx("p", { style: { color: '#6b7280' }, children: "No git data available for this site." }));
22
+ }
23
+ if (data.error) {
24
+ return (_jsxs("p", { style: { color: '#ef4444', fontSize: '0.875rem' }, children: ["Error: ", data.error] }));
25
+ }
26
+ // Prepare table data
27
+ const tableData = (data.commits || []).map((commit) => ({
28
+ Date: new Date(commit.date).toLocaleDateString(),
29
+ Message: _jsx("span", { className: "max-w-xs truncate inline-block", title: commit.message, children: commit.message }),
30
+ Version: commit.version ? (_jsx("span", { className: "px-2 py-1 text-xs bg-green-100 text-green-800 rounded", children: commit.version.split('~')[0] })) : (_jsx("span", { className: "text-gray-400", children: "-" }))
31
+ }));
32
+ return (_jsxs(_Fragment, { children: [_jsx("h4", { className: "health-site-name", children: siteName.replace('-', ' ') }), _jsx("div", { className: "space-y-4", children: tableData.length === 0 ? (_jsx("p", { className: "text-gray-500 text-center py-4", children: "No recent commits found" })) : (_jsx(Table, { id: "git-table", data: tableData, altRowColor: "#DDD" })) }), _jsxs("p", { className: "health-timestamp", children: ["Last checked: ", new Date(data.timestamp).toLocaleString()] })] }));
33
+ } }));
34
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Google Analytics Integration Services
3
+ * Server-side utilities for Google Analytics data retrieval
4
+ */
5
+ "use server";
6
+ import { RouteCache } from './site-health-cache';
7
+ import { createAnalyticsClient } from './google-api-auth';
8
+ // Cache for analytics data (1 hour)
9
+ const analyticsCache = new RouteCache();
10
+ /**
11
+ * Get Google Analytics data for a site with current/previous period comparison
12
+ */
13
+ export async function getGoogleAnalyticsData(config, siteName, startDate, endDate) {
14
+ try {
15
+ // Check cache first
16
+ const cacheKey = `analytics-${siteName}-${startDate || 'default'}-${endDate || 'default'}`;
17
+ const cached = analyticsCache.get(cacheKey);
18
+ if (cached) {
19
+ return { success: true, data: cached };
20
+ }
21
+ if (!config.ga4PropertyId || config.ga4PropertyId === 'GA4_PROPERTY_ID_HERE') {
22
+ return {
23
+ success: false,
24
+ error: 'GA4 Property ID not configured for this site'
25
+ };
26
+ }
27
+ // Set up authentication
28
+ const authResult = await createAnalyticsClient(config);
29
+ if (!authResult.success) {
30
+ return {
31
+ success: false,
32
+ error: authResult.error || 'Authentication failed'
33
+ };
34
+ }
35
+ const analyticsData = authResult.client;
36
+ // Calculate date ranges
37
+ const currentEndDate = endDate ? new Date(endDate) : new Date();
38
+ const currentStartDate = startDate ? new Date(startDate) : new Date(currentEndDate.getTime() - 30 * 24 * 60 * 60 * 1000);
39
+ // Calculate previous period (same duration before the current period)
40
+ const periodDuration = currentEndDate.getTime() - currentStartDate.getTime();
41
+ const previousEndDate = new Date(currentStartDate.getTime() - 24 * 60 * 60 * 1000); // One day before start
42
+ const previousStartDate = new Date(previousEndDate.getTime() - periodDuration);
43
+ const currentStartStr = currentStartDate.toISOString().split('T')[0];
44
+ const currentEndStr = currentEndDate.toISOString().split('T')[0];
45
+ const previousStartStr = previousStartDate.toISOString().split('T')[0];
46
+ const previousEndStr = previousEndDate.toISOString().split('T')[0];
47
+ // Fetch current period data
48
+ const currentResponse = await analyticsData.properties.runReport({
49
+ property: `properties/${config.ga4PropertyId}`,
50
+ requestBody: {
51
+ dateRanges: [{ startDate: currentStartStr, endDate: currentEndStr }],
52
+ dimensions: [{ name: 'date' }],
53
+ metrics: [{ name: 'screenPageViews' }],
54
+ orderBys: [{ dimension: { dimensionName: 'date' } }],
55
+ },
56
+ });
57
+ // Fetch previous period data
58
+ const previousResponse = await analyticsData.properties.runReport({
59
+ property: `properties/${config.ga4PropertyId}`,
60
+ requestBody: {
61
+ dateRanges: [{ startDate: previousStartStr, endDate: previousEndStr }],
62
+ dimensions: [{ name: 'date' }],
63
+ metrics: [{ name: 'screenPageViews' }],
64
+ orderBys: [{ dimension: { dimensionName: 'date' } }],
65
+ },
66
+ });
67
+ // Create a map of previous period data by date
68
+ const previousDataMap = new Map();
69
+ previousResponse.data.rows?.forEach((row) => {
70
+ const dateStr = row.dimensionValues?.[0]?.value || '';
71
+ if (dateStr) {
72
+ previousDataMap.set(dateStr, parseInt(row.metricValues?.[0]?.value || '0'));
73
+ }
74
+ });
75
+ // Combine current and previous period data
76
+ const chartData = [];
77
+ const daysInRange = Math.ceil((currentEndDate.getTime() - currentStartDate.getTime()) / (24 * 60 * 60 * 1000));
78
+ for (let i = daysInRange - 1; i >= 0; i--) {
79
+ const currentDate = new Date(currentEndDate);
80
+ currentDate.setDate(currentDate.getDate() - i);
81
+ const currentDateStr = currentDate.toISOString().split('T')[0].replace(/-/g, ''); // YYYYMMDD format
82
+ // Calculate corresponding previous period date
83
+ const previousDate = new Date(currentDate.getTime() - periodDuration);
84
+ const previousDateStr = previousDate.toISOString().split('T')[0].replace(/-/g, ''); // YYYYMMDD format
85
+ // Get current period data
86
+ const currentRow = currentResponse.data.rows?.find((row) => row.dimensionValues?.[0]?.value === currentDateStr);
87
+ const currentPageViews = parseInt(currentRow?.metricValues?.[0]?.value || '0');
88
+ // Get previous period data
89
+ const previousPageViews = previousDataMap.get(previousDateStr) || 0;
90
+ // Format date for display
91
+ const formattedDate = currentDate.toLocaleDateString('en-US', {
92
+ month: 'short',
93
+ day: 'numeric'
94
+ });
95
+ chartData.push({
96
+ date: formattedDate,
97
+ currentPageViews: currentPageViews,
98
+ previousPageViews: previousPageViews,
99
+ });
100
+ }
101
+ // Cache the result
102
+ analyticsCache.set(cacheKey, chartData);
103
+ return { success: true, data: chartData };
104
+ }
105
+ catch (error) {
106
+ console.error('Google Analytics error:', error);
107
+ return {
108
+ success: false,
109
+ error: error.message
110
+ };
111
+ }
112
+ }
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ComposedChart, Bar, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
4
+ import { SiteHealthTemplate } from './site-health-template';
5
+ export function SiteHealthGoogleAnalytics({ siteName, startDate, endDate }) {
6
+ const fetchAnalyticsData = async (site) => {
7
+ const params = new URLSearchParams({ siteName: site });
8
+ if (startDate)
9
+ params.append('startDate', startDate);
10
+ if (endDate)
11
+ params.append('endDate', endDate);
12
+ const response = await fetch(`/api/site-health/google-analytics?${params.toString()}`);
13
+ if (!response.ok) {
14
+ throw new Error(`Failed to fetch analytics data: ${response.status}`);
15
+ }
16
+ const result = await response.json();
17
+ if (!result.success) {
18
+ // Handle specific error types
19
+ if (result.error?.includes('invalid_grant') || result.error?.includes('authentication')) {
20
+ throw new Error('Google Analytics authentication expired. Please re-authorize the application.');
21
+ }
22
+ else if (result.error?.includes('GA4 Property ID not configured')) {
23
+ throw new Error('GA4 Property ID not configured for this site');
24
+ }
25
+ else {
26
+ throw new Error(result.error || 'Failed to load analytics data');
27
+ }
28
+ }
29
+ return result.data;
30
+ };
31
+ return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "Google Analytics", columnSpan: 2, fetchData: fetchAnalyticsData, children: (data) => {
32
+ if (!data || data.length === 0) {
33
+ return (_jsx("div", { className: "flex items-center justify-center h-64", children: _jsx("div", { className: "text-gray-500", children: "No data available for the selected date range" }) }));
34
+ }
35
+ return (_jsx("div", { children: _jsx("div", { style: { width: '100%', height: '400px', border: '1px solid #ddd' }, children: _jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(ComposedChart, { data: data, margin: { top: 40, right: 30, left: 20, bottom: 5 }, children: [_jsx("text", { x: "50%", y: 20, textAnchor: "middle", fontSize: "16", fontWeight: "bold", fill: "#374151", children: "Page Views (Current vs Previous Period)" }), _jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "date", tick: { fontSize: 12 }, angle: -45, textAnchor: "end", height: 60 }), _jsx(YAxis, { tick: { fontSize: 12 } }), _jsx(Tooltip, { formatter: (value, name) => [
36
+ value?.toLocaleString() || '0',
37
+ name || 'Unknown'
38
+ ], labelFormatter: (label) => `Date: ${label}` }), _jsx(Legend, { wrapperStyle: {
39
+ fontSize: '12px',
40
+ paddingTop: '10px'
41
+ } }), _jsx(Bar, { dataKey: "currentPageViews", fill: "#3b82f6", name: "Current Period", radius: [2, 2, 0, 0] }), _jsx(Line, { type: "monotone", dataKey: "previousPageViews", stroke: "#ef4444", strokeWidth: 2, strokeDasharray: "5 5", dot: { fill: '#ef4444', strokeWidth: 2, r: 3 }, activeDot: { r: 5, stroke: '#ef4444', strokeWidth: 2 }, name: "Previous Period" })] }, `chart-${data.length}`) }) }) }));
42
+ } }));
43
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Google Search Console Integration Services
3
+ * Server-side utilities for Google Search Console data retrieval
4
+ */
5
+ "use server";
6
+ import { RouteCache } from './site-health-cache';
7
+ import { createSearchConsoleClient } from './google-api-auth';
8
+ // Cache for search console data (1 hour)
9
+ const searchConsoleCache = new RouteCache();
10
+ /**
11
+ * Get Google Search Console data for a site with current/previous period comparison
12
+ */
13
+ export async function getSearchConsoleData(config, siteName, startDate, endDate) {
14
+ try {
15
+ // Check cache first
16
+ const cacheKey = `searchconsole-${siteName}-${startDate || 'default'}-${endDate || 'default'}`;
17
+ const cached = searchConsoleCache.get(cacheKey);
18
+ if (cached) {
19
+ return { success: true, data: cached };
20
+ }
21
+ if (!config.siteUrl) {
22
+ return {
23
+ success: false,
24
+ error: 'Site URL not configured for Search Console'
25
+ };
26
+ }
27
+ // Set up authentication
28
+ const authResult = await createSearchConsoleClient(config);
29
+ if (!authResult.success) {
30
+ return {
31
+ success: false,
32
+ error: authResult.error || 'Authentication failed'
33
+ };
34
+ }
35
+ const searchconsole = authResult.client;
36
+ // Calculate date ranges
37
+ const currentEndDate = endDate ? new Date(endDate) : new Date();
38
+ const currentStartDate = startDate ? new Date(startDate) : new Date(currentEndDate.getTime() - 30 * 24 * 60 * 60 * 1000);
39
+ // Calculate previous period (same duration before the current period)
40
+ const periodDuration = currentEndDate.getTime() - currentStartDate.getTime();
41
+ const previousEndDate = new Date(currentStartDate.getTime() - 24 * 60 * 60 * 1000); // One day before start
42
+ const previousStartDate = new Date(previousEndDate.getTime() - periodDuration);
43
+ const currentStartStr = currentStartDate.toISOString().split('T')[0];
44
+ const currentEndStr = currentEndDate.toISOString().split('T')[0];
45
+ const previousStartStr = previousStartDate.toISOString().split('T')[0];
46
+ const previousEndStr = previousEndDate.toISOString().split('T')[0];
47
+ // Fetch current period data
48
+ const currentResponse = await searchconsole.searchanalytics.query({
49
+ siteUrl: config.siteUrl,
50
+ requestBody: {
51
+ startDate: currentStartStr,
52
+ endDate: currentEndStr,
53
+ dimensions: ['date'],
54
+ rowLimit: 10000,
55
+ },
56
+ });
57
+ // Fetch previous period data
58
+ const previousResponse = await searchconsole.searchanalytics.query({
59
+ siteUrl: config.siteUrl,
60
+ requestBody: {
61
+ startDate: previousStartStr,
62
+ endDate: previousEndStr,
63
+ dimensions: ['date'],
64
+ rowLimit: 10000,
65
+ },
66
+ });
67
+ // Create a map of previous period data by date
68
+ const previousDataMap = new Map();
69
+ previousResponse.data.rows?.forEach((row) => {
70
+ const dateStr = row.keys?.[0] || '';
71
+ if (dateStr) {
72
+ previousDataMap.set(dateStr, {
73
+ clicks: parseFloat(String(row.clicks || '0')),
74
+ impressions: parseFloat(String(row.impressions || '0'))
75
+ });
76
+ }
77
+ });
78
+ // Combine current and previous period data
79
+ const chartData = [];
80
+ const daysInRange = Math.ceil((currentEndDate.getTime() - currentStartDate.getTime()) / (24 * 60 * 60 * 1000));
81
+ for (let i = daysInRange - 1; i >= 0; i--) {
82
+ const currentDate = new Date(currentEndDate);
83
+ currentDate.setDate(currentDate.getDate() - i);
84
+ const currentDateStr = currentDate.toISOString().split('T')[0];
85
+ // Calculate corresponding previous period date
86
+ const previousDate = new Date(currentDate.getTime() - periodDuration);
87
+ const previousDateStr = previousDate.toISOString().split('T')[0];
88
+ // Get current period data
89
+ const currentRow = currentResponse.data.rows?.find((row) => row.keys?.[0] === currentDateStr);
90
+ const currentClicks = parseFloat(String(currentRow?.clicks || '0'));
91
+ const currentImpressions = parseFloat(String(currentRow?.impressions || '0'));
92
+ // Get previous period data
93
+ const previousData = previousDataMap.get(previousDateStr) || { clicks: 0, impressions: 0 };
94
+ // Format date for display
95
+ const formattedDate = currentDate.toLocaleDateString('en-US', {
96
+ month: 'short',
97
+ day: 'numeric'
98
+ });
99
+ chartData.push({
100
+ date: formattedDate,
101
+ currentImpressions: Math.round(currentImpressions),
102
+ currentClicks: Math.round(currentClicks),
103
+ previousImpressions: Math.round(previousData.impressions),
104
+ previousClicks: Math.round(previousData.clicks),
105
+ });
106
+ }
107
+ // Cache the result
108
+ searchConsoleCache.set(cacheKey, chartData);
109
+ return { success: true, data: chartData };
110
+ }
111
+ catch (error) {
112
+ console.error('Google Search Console error:', error);
113
+ return {
114
+ success: false,
115
+ error: error.message
116
+ };
117
+ }
118
+ }