claude-plugin-wordpress-manager 1.4.0 → 1.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.
Files changed (81) hide show
  1. package/.claude-plugin/plugin.json +7 -3
  2. package/CHANGELOG.md +111 -0
  3. package/README.md +10 -3
  4. package/agents/wp-accessibility-auditor.md +206 -0
  5. package/agents/wp-content-strategist.md +18 -0
  6. package/agents/wp-deployment-engineer.md +34 -2
  7. package/agents/wp-performance-optimizer.md +12 -0
  8. package/agents/wp-security-auditor.md +20 -0
  9. package/agents/wp-security-hardener.md +266 -0
  10. package/agents/wp-site-manager.md +14 -0
  11. package/agents/wp-test-engineer.md +207 -0
  12. package/docs/GUIDE.md +68 -15
  13. package/docs/guides/INDEX.md +46 -0
  14. package/docs/guides/wp-blog.md +590 -0
  15. package/docs/guides/wp-design-system.md +976 -0
  16. package/docs/guides/wp-ecommerce.md +786 -0
  17. package/docs/guides/wp-landing-page.md +762 -0
  18. package/docs/guides/wp-portfolio.md +713 -0
  19. package/docs/plans/2026-02-27-design-system-guide-design.md +30 -0
  20. package/docs/plans/2026-02-27-local-dev-tools-assessment.md +332 -0
  21. package/docs/plans/2026-02-27-local-env-design.md +179 -0
  22. package/docs/plans/2026-02-27-site-type-guides-design.md +44 -0
  23. package/package.json +7 -3
  24. package/skills/wordpress-router/SKILL.md +25 -5
  25. package/skills/wordpress-router/references/decision-tree.md +59 -3
  26. package/skills/wp-accessibility/SKILL.md +170 -0
  27. package/skills/wp-accessibility/references/a11y-audit-tools.md +248 -0
  28. package/skills/wp-accessibility/references/a11y-testing.md +222 -0
  29. package/skills/wp-accessibility/references/block-a11y.md +247 -0
  30. package/skills/wp-accessibility/references/interactive-a11y.md +272 -0
  31. package/skills/wp-accessibility/references/media-a11y.md +254 -0
  32. package/skills/wp-accessibility/references/theme-a11y.md +309 -0
  33. package/skills/wp-audit/SKILL.md +4 -0
  34. package/skills/wp-block-development/SKILL.md +5 -0
  35. package/skills/wp-block-themes/SKILL.md +4 -0
  36. package/skills/wp-deploy/SKILL.md +12 -0
  37. package/skills/wp-e2e-testing/SKILL.md +186 -0
  38. package/skills/wp-e2e-testing/references/ci-integration.md +174 -0
  39. package/skills/wp-e2e-testing/references/jest-wordpress.md +114 -0
  40. package/skills/wp-e2e-testing/references/phpunit-wordpress.md +141 -0
  41. package/skills/wp-e2e-testing/references/playwright-wordpress.md +108 -0
  42. package/skills/wp-e2e-testing/references/test-data-generation.md +127 -0
  43. package/skills/wp-e2e-testing/references/visual-regression.md +107 -0
  44. package/skills/wp-e2e-testing/references/wp-env-setup.md +97 -0
  45. package/skills/wp-e2e-testing/scripts/test_inspect.mjs +375 -0
  46. package/skills/wp-headless/SKILL.md +168 -0
  47. package/skills/wp-headless/references/api-layer-choice.md +160 -0
  48. package/skills/wp-headless/references/cors-config.md +245 -0
  49. package/skills/wp-headless/references/frontend-integration.md +331 -0
  50. package/skills/wp-headless/references/headless-auth.md +286 -0
  51. package/skills/wp-headless/references/webhooks.md +277 -0
  52. package/skills/wp-headless/references/wpgraphql.md +331 -0
  53. package/skills/wp-headless/scripts/headless_inspect.mjs +321 -0
  54. package/skills/wp-i18n/SKILL.md +170 -0
  55. package/skills/wp-i18n/references/js-i18n.md +201 -0
  56. package/skills/wp-i18n/references/multilingual-setup.md +219 -0
  57. package/skills/wp-i18n/references/php-i18n.md +196 -0
  58. package/skills/wp-i18n/references/rtl-support.md +206 -0
  59. package/skills/wp-i18n/references/translation-workflow.md +178 -0
  60. package/skills/wp-i18n/references/wpcli-i18n.md +177 -0
  61. package/skills/wp-i18n/scripts/i18n_inspect.mjs +330 -0
  62. package/skills/wp-interactivity-api/SKILL.md +4 -0
  63. package/skills/wp-local-env/SKILL.md +233 -0
  64. package/skills/wp-local-env/references/localwp-adapter.md +156 -0
  65. package/skills/wp-local-env/references/mcp-adapter-setup.md +153 -0
  66. package/skills/wp-local-env/references/studio-adapter.md +127 -0
  67. package/skills/wp-local-env/references/wpenv-adapter.md +121 -0
  68. package/skills/wp-local-env/scripts/detect_local_env.mjs +404 -0
  69. package/skills/wp-playground/SKILL.md +13 -1
  70. package/skills/wp-plugin-development/SKILL.md +6 -0
  71. package/skills/wp-rest-api/SKILL.md +4 -0
  72. package/skills/wp-security/SKILL.md +179 -0
  73. package/skills/wp-security/references/api-restriction.md +147 -0
  74. package/skills/wp-security/references/authentication-hardening.md +105 -0
  75. package/skills/wp-security/references/filesystem-hardening.md +105 -0
  76. package/skills/wp-security/references/http-headers.md +105 -0
  77. package/skills/wp-security/references/incident-response.md +144 -0
  78. package/skills/wp-security/references/user-capabilities.md +115 -0
  79. package/skills/wp-security/references/wp-config-security.md +129 -0
  80. package/skills/wp-security/scripts/security_inspect.mjs +393 -0
  81. package/skills/wp-wpcli-and-ops/SKILL.md +6 -0
@@ -0,0 +1,107 @@
1
+ # Visual Regression Testing
2
+
3
+ Use this file when adding screenshot-based visual regression tests to a WordPress project.
4
+
5
+ ## Playwright built-in approach (recommended)
6
+
7
+ Playwright includes `toHaveScreenshot()` for visual comparison:
8
+
9
+ ```ts
10
+ import { test, expect } from '@wordpress/e2e-test-utils-playwright';
11
+
12
+ test('homepage matches visual baseline', async ({ page }) => {
13
+ await page.goto('/');
14
+ await expect(page).toHaveScreenshot('homepage.png', {
15
+ maxDiffPixelRatio: 0.01, // Allow 1% pixel difference
16
+ });
17
+ });
18
+
19
+ test('block renders correctly in editor', async ({ admin, editor, page }) => {
20
+ await admin.visitAdminPage('post-new.php');
21
+ await editor.insertBlock({ name: 'my-plugin/my-block' });
22
+ const block = editor.canvas.locator('[data-type="my-plugin/my-block"]');
23
+ await expect(block).toHaveScreenshot('my-block-editor.png');
24
+ });
25
+ ```
26
+
27
+ ## Generating baselines
28
+
29
+ ```bash
30
+ # First run: creates baseline screenshots
31
+ npx playwright test --update-snapshots
32
+
33
+ # Subsequent runs: compare against baselines
34
+ npx playwright test
35
+ ```
36
+
37
+ Baselines are stored in `tests/e2e/__snapshots__/` by default.
38
+
39
+ ## Handling dynamic content
40
+
41
+ Mask elements that change between runs:
42
+
43
+ ```ts
44
+ await expect(page).toHaveScreenshot('dashboard.png', {
45
+ mask: [
46
+ page.locator('.current-time'),
47
+ page.locator('.random-ad'),
48
+ page.locator('#wpadminbar'), // Admin bar may show user-specific data
49
+ ],
50
+ });
51
+ ```
52
+
53
+ ## Disable animations
54
+
55
+ Add to `playwright.config.ts` to prevent animation-related flakiness:
56
+
57
+ ```ts
58
+ use: {
59
+ // ...
60
+ contextOptions: {
61
+ reducedMotion: 'reduce',
62
+ },
63
+ },
64
+ ```
65
+
66
+ ## Consistent viewport
67
+
68
+ ```ts
69
+ use: {
70
+ viewport: { width: 1280, height: 720 },
71
+ },
72
+ ```
73
+
74
+ ## CI integration
75
+
76
+ Playwright stores screenshots as test artifacts. In GitHub Actions:
77
+
78
+ ```yaml
79
+ - uses: actions/upload-artifact@v4
80
+ if: failure()
81
+ with:
82
+ name: visual-regression-diffs
83
+ path: tests/e2e/artifacts/
84
+ retention-days: 7
85
+ ```
86
+
87
+ ## Updating baselines after intentional changes
88
+
89
+ ```bash
90
+ npx playwright test --update-snapshots
91
+ git add tests/e2e/__snapshots__/
92
+ git commit -m "Update visual regression baselines"
93
+ ```
94
+
95
+ ## Threshold tuning
96
+
97
+ - `maxDiffPixelRatio: 0.01` — 1% tolerance (good default)
98
+ - `maxDiffPixels: 100` — absolute pixel count tolerance
99
+ - `threshold: 0.2` — per-pixel color sensitivity (0-1, lower = stricter)
100
+
101
+ Use higher thresholds for pages with web fonts (rendering varies across OS).
102
+
103
+ ## Common issues
104
+
105
+ - **False positives on CI**: OS font rendering differs; consider running in Docker or using consistent font stacks
106
+ - **Flaky screenshots**: add `await page.waitForLoadState('networkidle')` before screenshots; mask dynamic elements
107
+ - **Large snapshot files**: use PNG compression; store in Git LFS for large projects
@@ -0,0 +1,97 @@
1
+ # wp-env Test Environment Setup
2
+
3
+ Use this file when setting up or configuring wp-env as a test environment for WordPress development.
4
+
5
+ ## Minimal `.wp-env.json` for testing
6
+
7
+ ```json
8
+ {
9
+ "core": null,
10
+ "phpVersion": "8.2",
11
+ "plugins": ["./"],
12
+ "config": {
13
+ "WP_DEBUG": true,
14
+ "SCRIPT_DEBUG": true
15
+ },
16
+ "port": 8888,
17
+ "testsPort": 8889
18
+ }
19
+ ```
20
+
21
+ For a theme project, replace `"plugins": ["./"]` with `"themes": ["./"]`.
22
+
23
+ ## Starting and managing
24
+
25
+ ```bash
26
+ npx wp-env start # Start both dev and test environments
27
+ npx wp-env start --update # Start and pull latest images
28
+ npx wp-env stop # Stop containers (preserves data)
29
+ npx wp-env destroy # Remove containers and data
30
+ npx wp-env clean all # Reset databases only
31
+ ```
32
+
33
+ ## Running commands inside wp-env
34
+
35
+ ```bash
36
+ # WP-CLI in development environment
37
+ npx wp-env run cli wp plugin list
38
+
39
+ # WP-CLI in tests environment
40
+ npx wp-env run tests-cli wp option get siteurl
41
+
42
+ # PHPUnit in tests container
43
+ npx wp-env run tests-cli --env-cwd=wp-content/plugins/my-plugin phpunit
44
+
45
+ # Arbitrary bash
46
+ npx wp-env run cli bash -c "cat wp-config.php | grep WP_DEBUG"
47
+ ```
48
+
49
+ ## Default credentials
50
+
51
+ - **Development**: `http://localhost:8888` — admin / password
52
+ - **Tests**: `http://localhost:8889` — admin / password
53
+
54
+ ## Custom PHP and WP versions
55
+
56
+ ```json
57
+ {
58
+ "core": "WordPress/WordPress#6.8",
59
+ "phpVersion": "8.1"
60
+ }
61
+ ```
62
+
63
+ This is useful for testing compatibility matrices. Each CI job can override these values.
64
+
65
+ ## Mounting additional plugins/themes
66
+
67
+ ```json
68
+ {
69
+ "plugins": [
70
+ "./",
71
+ "https://downloads.wordpress.org/plugin/gutenberg.latest-stable.zip"
72
+ ],
73
+ "mappings": {
74
+ "wp-content/mu-plugins": "./test-utils/mu-plugins"
75
+ }
76
+ }
77
+ ```
78
+
79
+ ## Override file for local settings
80
+
81
+ Create `.wp-env.override.json` (gitignored) for developer-specific settings:
82
+
83
+ ```json
84
+ {
85
+ "port": 9999,
86
+ "config": {
87
+ "WP_DEBUG_LOG": true
88
+ }
89
+ }
90
+ ```
91
+
92
+ ## Common issues
93
+
94
+ - **Port conflict**: change `port`/`testsPort` or stop the conflicting service
95
+ - **Docker not running**: `docker info` must succeed; start Docker Desktop or daemon
96
+ - **Stale containers**: `npx wp-env destroy && npx wp-env start` for a clean slate
97
+ - **Plugin not activated**: run `npx wp-env run cli wp plugin activate my-plugin`
@@ -0,0 +1,375 @@
1
+ /**
2
+ * test_inspect.mjs — Detect testing frameworks and configuration in a WordPress project.
3
+ *
4
+ * Scans for Playwright, Jest, PHPUnit, wp-env, and CI config.
5
+ * Outputs a JSON report to stdout with detected frameworks,
6
+ * test directories, configuration files, and CI integration.
7
+ *
8
+ * Usage:
9
+ * node test_inspect.mjs [--cwd=/path/to/check]
10
+ *
11
+ * Exit codes:
12
+ * 0 — at least one test framework detected
13
+ * 1 — no test frameworks detected
14
+ */
15
+
16
+ import fs from "node:fs";
17
+ import path from "node:path";
18
+ import process from "node:process";
19
+ import { execSync } from "node:child_process";
20
+
21
+ const TOOL_VERSION = "1.0.0";
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Helpers
25
+ // ---------------------------------------------------------------------------
26
+
27
+ function statSafe(p) {
28
+ try {
29
+ return fs.statSync(p);
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ function readFileSafe(p) {
36
+ try {
37
+ return fs.readFileSync(p, "utf8");
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ function readJsonSafe(p) {
44
+ const raw = readFileSafe(p);
45
+ if (!raw) return null;
46
+ try {
47
+ return JSON.parse(raw);
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+
53
+ function execSafe(cmd, cwd, timeoutMs = 5000) {
54
+ try {
55
+ return execSync(cmd, { encoding: "utf8", timeout: timeoutMs, cwd, stdio: ["pipe", "pipe", "pipe"] }).trim();
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ function globDirs(base, patterns) {
62
+ const found = [];
63
+ for (const pattern of patterns) {
64
+ const full = path.join(base, pattern);
65
+ if (statSafe(full)?.isDirectory()) {
66
+ found.push(pattern);
67
+ }
68
+ }
69
+ return found;
70
+ }
71
+
72
+ function globFiles(base, patterns) {
73
+ const found = [];
74
+ for (const pattern of patterns) {
75
+ const full = path.join(base, pattern);
76
+ if (statSafe(full)?.isFile()) {
77
+ found.push(pattern);
78
+ }
79
+ }
80
+ return found;
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Parse --cwd argument
85
+ // ---------------------------------------------------------------------------
86
+
87
+ function parseCwd() {
88
+ const cwdArg = process.argv.find((a) => a.startsWith("--cwd="));
89
+ return cwdArg ? cwdArg.slice(6) : process.cwd();
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Detect Playwright
94
+ // ---------------------------------------------------------------------------
95
+
96
+ function detectPlaywright(cwd) {
97
+ const result = { detected: false, configFile: null, testDirs: [], wpE2eUtils: false };
98
+
99
+ const configFiles = [
100
+ "playwright.config.js",
101
+ "playwright.config.ts",
102
+ "playwright.config.mjs",
103
+ ];
104
+ const found = globFiles(cwd, configFiles);
105
+ if (found.length > 0) {
106
+ result.detected = true;
107
+ result.configFile = found[0];
108
+ }
109
+
110
+ const testDirs = globDirs(cwd, [
111
+ "tests/e2e",
112
+ "tests/playwright",
113
+ "e2e",
114
+ "test/e2e",
115
+ "specs",
116
+ ]);
117
+ result.testDirs = testDirs;
118
+
119
+ // Check for @wordpress/e2e-test-utils-playwright
120
+ const pkg = readJsonSafe(path.join(cwd, "package.json"));
121
+ if (pkg) {
122
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
123
+ if (allDeps["@wordpress/e2e-test-utils-playwright"]) {
124
+ result.wpE2eUtils = true;
125
+ result.detected = true;
126
+ }
127
+ if (allDeps["@playwright/test"] || allDeps["playwright"]) {
128
+ result.detected = true;
129
+ }
130
+ }
131
+
132
+ return result;
133
+ }
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Detect Jest
137
+ // ---------------------------------------------------------------------------
138
+
139
+ function detectJest(cwd) {
140
+ const result = { detected: false, configFile: null, testDirs: [], wpScripts: false };
141
+
142
+ const configFiles = [
143
+ "jest.config.js",
144
+ "jest.config.ts",
145
+ "jest.config.mjs",
146
+ "jest.config.json",
147
+ ];
148
+ const found = globFiles(cwd, configFiles);
149
+ if (found.length > 0) {
150
+ result.detected = true;
151
+ result.configFile = found[0];
152
+ }
153
+
154
+ // Check package.json for jest config
155
+ const pkg = readJsonSafe(path.join(cwd, "package.json"));
156
+ if (pkg) {
157
+ if (pkg.jest) {
158
+ result.detected = true;
159
+ result.configFile = result.configFile || "package.json (jest key)";
160
+ }
161
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
162
+ if (allDeps["@wordpress/scripts"]) {
163
+ result.wpScripts = true;
164
+ result.detected = true;
165
+ }
166
+ if (allDeps["jest"]) {
167
+ result.detected = true;
168
+ }
169
+ }
170
+
171
+ const testDirs = globDirs(cwd, [
172
+ "tests/js",
173
+ "tests/unit",
174
+ "tests/jest",
175
+ "src/__tests__",
176
+ "__tests__",
177
+ "test/js",
178
+ ]);
179
+ result.testDirs = testDirs;
180
+
181
+ return result;
182
+ }
183
+
184
+ // ---------------------------------------------------------------------------
185
+ // Detect PHPUnit
186
+ // ---------------------------------------------------------------------------
187
+
188
+ function detectPHPUnit(cwd) {
189
+ const result = { detected: false, configFile: null, testDirs: [], bootstrap: null };
190
+
191
+ const configFiles = [
192
+ "phpunit.xml",
193
+ "phpunit.xml.dist",
194
+ "phpunit.dist.xml",
195
+ ];
196
+ const found = globFiles(cwd, configFiles);
197
+ if (found.length > 0) {
198
+ result.detected = true;
199
+ result.configFile = found[0];
200
+
201
+ // Parse bootstrap path from XML
202
+ const content = readFileSafe(path.join(cwd, found[0]));
203
+ if (content) {
204
+ const match = content.match(/bootstrap="([^"]+)"/);
205
+ if (match) result.bootstrap = match[1];
206
+ }
207
+ }
208
+
209
+ const testDirs = globDirs(cwd, [
210
+ "tests/phpunit",
211
+ "tests/php",
212
+ "tests/unit",
213
+ "tests",
214
+ ]);
215
+ result.testDirs = testDirs;
216
+
217
+ // Check composer.json
218
+ const composer = readJsonSafe(path.join(cwd, "composer.json"));
219
+ if (composer) {
220
+ const allDeps = { ...composer.require, ...composer["require-dev"] };
221
+ if (allDeps["phpunit/phpunit"] || allDeps["yoast/phpunit-polyfills"]) {
222
+ result.detected = true;
223
+ }
224
+ }
225
+
226
+ return result;
227
+ }
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // Detect wp-env
231
+ // ---------------------------------------------------------------------------
232
+
233
+ function detectWpEnv(cwd) {
234
+ const result = { detected: false, configFile: null, running: false };
235
+
236
+ if (statSafe(path.join(cwd, ".wp-env.json"))?.isFile()) {
237
+ result.detected = true;
238
+ result.configFile = ".wp-env.json";
239
+ }
240
+
241
+ if (statSafe(path.join(cwd, ".wp-env.override.json"))?.isFile()) {
242
+ result.detected = true;
243
+ }
244
+
245
+ const pkg = readJsonSafe(path.join(cwd, "package.json"));
246
+ if (pkg) {
247
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
248
+ if (allDeps["@wordpress/env"]) {
249
+ result.detected = true;
250
+ }
251
+ }
252
+
253
+ // Check if wp-env is running (Docker containers)
254
+ const dockerCheck = execSafe("docker ps --filter name=wp-env --format '{{.Names}}'", cwd);
255
+ if (dockerCheck && dockerCheck.length > 0) {
256
+ result.running = true;
257
+ }
258
+
259
+ return result;
260
+ }
261
+
262
+ // ---------------------------------------------------------------------------
263
+ // Detect CI configuration
264
+ // ---------------------------------------------------------------------------
265
+
266
+ function detectCI(cwd) {
267
+ const result = { detected: false, provider: null, hasTestStep: false };
268
+
269
+ // GitHub Actions
270
+ const ghDirs = globDirs(cwd, [".github/workflows"]);
271
+ if (ghDirs.length > 0) {
272
+ const workflowDir = path.join(cwd, ".github", "workflows");
273
+ try {
274
+ const files = fs.readdirSync(workflowDir);
275
+ for (const file of files) {
276
+ if (file.endsWith(".yml") || file.endsWith(".yaml")) {
277
+ result.detected = true;
278
+ result.provider = "github-actions";
279
+ const content = readFileSafe(path.join(workflowDir, file));
280
+ if (content && /phpunit|jest|playwright|wp-env|npm test|npm run test/i.test(content)) {
281
+ result.hasTestStep = true;
282
+ }
283
+ }
284
+ }
285
+ } catch { /* ignore */ }
286
+ }
287
+
288
+ // GitLab CI
289
+ if (statSafe(path.join(cwd, ".gitlab-ci.yml"))?.isFile()) {
290
+ result.detected = true;
291
+ result.provider = result.provider || "gitlab-ci";
292
+ const content = readFileSafe(path.join(cwd, ".gitlab-ci.yml"));
293
+ if (content && /phpunit|jest|playwright|wp-env/i.test(content)) {
294
+ result.hasTestStep = true;
295
+ }
296
+ }
297
+
298
+ return result;
299
+ }
300
+
301
+ // ---------------------------------------------------------------------------
302
+ // Detect npm test scripts
303
+ // ---------------------------------------------------------------------------
304
+
305
+ function detectScripts(cwd) {
306
+ const pkg = readJsonSafe(path.join(cwd, "package.json"));
307
+ if (!pkg?.scripts) return {};
308
+
309
+ const testScripts = {};
310
+ for (const [key, value] of Object.entries(pkg.scripts)) {
311
+ if (/test|e2e|playwright|jest|phpunit/i.test(key)) {
312
+ testScripts[key] = value;
313
+ }
314
+ }
315
+ return testScripts;
316
+ }
317
+
318
+ // ---------------------------------------------------------------------------
319
+ // Main
320
+ // ---------------------------------------------------------------------------
321
+
322
+ function main() {
323
+ const cwd = parseCwd();
324
+
325
+ if (!statSafe(cwd)?.isDirectory()) {
326
+ console.error(`Error: directory not found: ${cwd}`);
327
+ process.exit(1);
328
+ }
329
+
330
+ const playwright = detectPlaywright(cwd);
331
+ const jest = detectJest(cwd);
332
+ const phpunit = detectPHPUnit(cwd);
333
+ const wpEnv = detectWpEnv(cwd);
334
+ const ci = detectCI(cwd);
335
+ const scripts = detectScripts(cwd);
336
+
337
+ const anyDetected = playwright.detected || jest.detected || phpunit.detected;
338
+
339
+ const report = {
340
+ tool: "test_inspect",
341
+ version: TOOL_VERSION,
342
+ cwd,
343
+ detected: anyDetected,
344
+ frameworks: {
345
+ playwright,
346
+ jest,
347
+ phpunit,
348
+ },
349
+ environment: {
350
+ wpEnv,
351
+ },
352
+ ci,
353
+ scripts,
354
+ recommendations: [],
355
+ };
356
+
357
+ // Generate recommendations
358
+ if (!anyDetected) {
359
+ report.recommendations.push("No test frameworks detected. Consider adding Playwright for E2E and PHPUnit for unit tests.");
360
+ }
361
+ if (!wpEnv.detected && (playwright.detected || phpunit.detected)) {
362
+ report.recommendations.push("Consider adding .wp-env.json for a consistent WordPress test environment.");
363
+ }
364
+ if (anyDetected && !ci.hasTestStep) {
365
+ report.recommendations.push("Test frameworks detected but CI does not run tests. Add a test step to your CI pipeline.");
366
+ }
367
+ if (playwright.detected && !playwright.wpE2eUtils) {
368
+ report.recommendations.push("Consider using @wordpress/e2e-test-utils-playwright for WordPress-specific test helpers.");
369
+ }
370
+
371
+ console.log(JSON.stringify(report, null, 2));
372
+ process.exit(anyDetected ? 0 : 1);
373
+ }
374
+
375
+ main();