wordpress-agent-kit 0.5.1 → 0.6.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 (132) hide show
  1. package/.agents/skills/wp-bootstrap/SKILL.md +314 -0
  2. package/.agents/skills/wp-bootstrap/references/composer-setup.md +275 -0
  3. package/.agents/skills/wp-bootstrap/references/monorepo-patterns.md +184 -0
  4. package/.agents/skills/wp-bootstrap/scripts/bootstrap.sh +151 -0
  5. package/.agents/skills/wp-bootstrap/scripts/detect-structure.mjs +466 -0
  6. package/.agents/skills/wp-bootstrap/scripts/package-wp.sh +173 -0
  7. package/.agents/skills/wp-bootstrap/scripts/playground-start.sh +148 -0
  8. package/.agents/skills/wp-bootstrap/scripts/playground-verify.sh +165 -0
  9. package/.agents/skills/wp-bootstrap/scripts/setup-github.sh +417 -0
  10. package/.agents/skills/wp-wpengine/SKILL.md +76 -12
  11. package/.agents/skills/wp-wpengine/references/github-actions-deploy.md +16 -9
  12. package/README.md +1 -1
  13. package/dist/cli.js +2 -0
  14. package/dist/commands/bootstrap.js +105 -0
  15. package/dist/lib/api.js +1 -0
  16. package/dist/lib/bootstrap.js +352 -0
  17. package/extensions/wp-agent-kit/index.ts +143 -3
  18. package/package.json +1 -1
  19. package/skills-custom/wp-bootstrap/SKILL.md +314 -0
  20. package/skills-custom/wp-bootstrap/references/composer-setup.md +275 -0
  21. package/skills-custom/wp-bootstrap/references/monorepo-patterns.md +184 -0
  22. package/skills-custom/wp-bootstrap/scripts/bootstrap.sh +151 -0
  23. package/skills-custom/wp-bootstrap/scripts/detect-structure.mjs +466 -0
  24. package/skills-custom/wp-bootstrap/scripts/package-wp.sh +173 -0
  25. package/skills-custom/wp-bootstrap/scripts/playground-start.sh +148 -0
  26. package/skills-custom/wp-bootstrap/scripts/playground-verify.sh +165 -0
  27. package/skills-custom/wp-bootstrap/scripts/setup-github.sh +417 -0
  28. package/skills-custom/wp-wpengine/SKILL.md +76 -12
  29. package/skills-custom/wp-wpengine/references/github-actions-deploy.md +16 -9
  30. package/.github/skills/blueprint/SKILL.md +0 -418
  31. package/.github/skills/wordpress-router/SKILL.md +0 -52
  32. package/.github/skills/wordpress-router/references/decision-tree.md +0 -55
  33. package/.github/skills/wp-abilities-api/SKILL.md +0 -108
  34. package/.github/skills/wp-abilities-api/references/delegate-helper-pattern.md +0 -241
  35. package/.github/skills/wp-abilities-api/references/domain-vs-projection.md +0 -113
  36. package/.github/skills/wp-abilities-api/references/error-code-vocabulary.md +0 -123
  37. package/.github/skills/wp-abilities-api/references/grouping-heuristic.md +0 -89
  38. package/.github/skills/wp-abilities-api/references/input-schema-gotchas.md +0 -265
  39. package/.github/skills/wp-abilities-api/references/php-registration.md +0 -94
  40. package/.github/skills/wp-abilities-api/references/plugin-family-patterns.md +0 -233
  41. package/.github/skills/wp-abilities-api/references/rest-api.md +0 -13
  42. package/.github/skills/wp-abilities-api/references/shared-core-service.md +0 -184
  43. package/.github/skills/wp-abilities-audit/SKILL.md +0 -199
  44. package/.github/skills/wp-abilities-audit/references/audit-schema.md +0 -300
  45. package/.github/skills/wp-abilities-audit/references/capability-gate-tracing.md +0 -197
  46. package/.github/skills/wp-abilities-audit/references/controller-enumeration.md +0 -116
  47. package/.github/skills/wp-abilities-verify/SKILL.md +0 -215
  48. package/.github/skills/wp-abilities-verify/references/annotation-correctness.md +0 -154
  49. package/.github/skills/wp-abilities-verify/references/audit-schema-validation.md +0 -131
  50. package/.github/skills/wp-abilities-verify/references/permission-roundtrip.md +0 -190
  51. package/.github/skills/wp-abilities-verify/references/runtime-harness.md +0 -462
  52. package/.github/skills/wp-abilities-verify/references/schema-lints.md +0 -118
  53. package/.github/skills/wp-abilities-verify/references/static-enumeration.md +0 -126
  54. package/.github/skills/wp-block-development/SKILL.md +0 -175
  55. package/.github/skills/wp-block-development/references/attributes-and-serialization.md +0 -22
  56. package/.github/skills/wp-block-development/references/block-json.md +0 -49
  57. package/.github/skills/wp-block-development/references/creating-new-blocks.md +0 -46
  58. package/.github/skills/wp-block-development/references/debugging.md +0 -36
  59. package/.github/skills/wp-block-development/references/deprecations.md +0 -24
  60. package/.github/skills/wp-block-development/references/dynamic-rendering.md +0 -23
  61. package/.github/skills/wp-block-development/references/inner-blocks.md +0 -25
  62. package/.github/skills/wp-block-development/references/registration.md +0 -30
  63. package/.github/skills/wp-block-development/references/supports-and-wrappers.md +0 -18
  64. package/.github/skills/wp-block-development/references/tooling-and-testing.md +0 -21
  65. package/.github/skills/wp-block-development/scripts/list_blocks.mjs +0 -121
  66. package/.github/skills/wp-block-themes/SKILL.md +0 -117
  67. package/.github/skills/wp-block-themes/references/creating-new-block-theme.md +0 -37
  68. package/.github/skills/wp-block-themes/references/debugging.md +0 -24
  69. package/.github/skills/wp-block-themes/references/patterns.md +0 -18
  70. package/.github/skills/wp-block-themes/references/style-variations.md +0 -14
  71. package/.github/skills/wp-block-themes/references/templates-and-parts.md +0 -16
  72. package/.github/skills/wp-block-themes/references/theme-json.md +0 -59
  73. package/.github/skills/wp-block-themes/scripts/detect_block_themes.mjs +0 -117
  74. package/.github/skills/wp-interactivity-api/SKILL.md +0 -180
  75. package/.github/skills/wp-interactivity-api/references/debugging.md +0 -29
  76. package/.github/skills/wp-interactivity-api/references/directives-quickref.md +0 -30
  77. package/.github/skills/wp-interactivity-api/references/server-side-rendering.md +0 -310
  78. package/.github/skills/wp-performance/SKILL.md +0 -147
  79. package/.github/skills/wp-performance/references/autoload-options.md +0 -24
  80. package/.github/skills/wp-performance/references/cron.md +0 -20
  81. package/.github/skills/wp-performance/references/database.md +0 -20
  82. package/.github/skills/wp-performance/references/http-api.md +0 -15
  83. package/.github/skills/wp-performance/references/measurement.md +0 -21
  84. package/.github/skills/wp-performance/references/object-cache.md +0 -24
  85. package/.github/skills/wp-performance/references/query-monitor-headless.md +0 -38
  86. package/.github/skills/wp-performance/references/server-timing.md +0 -22
  87. package/.github/skills/wp-performance/references/wp-cli-doctor.md +0 -24
  88. package/.github/skills/wp-performance/references/wp-cli-profile.md +0 -32
  89. package/.github/skills/wp-performance/scripts/perf_inspect.mjs +0 -128
  90. package/.github/skills/wp-phpstan/SKILL.md +0 -98
  91. package/.github/skills/wp-phpstan/references/configuration.md +0 -52
  92. package/.github/skills/wp-phpstan/references/third-party-classes.md +0 -76
  93. package/.github/skills/wp-phpstan/references/wordpress-annotations.md +0 -124
  94. package/.github/skills/wp-phpstan/scripts/phpstan_inspect.mjs +0 -263
  95. package/.github/skills/wp-playground/SKILL.md +0 -233
  96. package/.github/skills/wp-playground/references/blueprints.md +0 -36
  97. package/.github/skills/wp-playground/references/cli-commands.md +0 -39
  98. package/.github/skills/wp-playground/references/debugging.md +0 -16
  99. package/.github/skills/wp-playground/references/e2e-playwright.md +0 -115
  100. package/.github/skills/wp-plugin-development/SKILL.md +0 -113
  101. package/.github/skills/wp-plugin-development/references/data-and-cron.md +0 -19
  102. package/.github/skills/wp-plugin-development/references/debugging.md +0 -19
  103. package/.github/skills/wp-plugin-development/references/lifecycle.md +0 -33
  104. package/.github/skills/wp-plugin-development/references/security.md +0 -29
  105. package/.github/skills/wp-plugin-development/references/settings-api.md +0 -22
  106. package/.github/skills/wp-plugin-development/references/structure.md +0 -16
  107. package/.github/skills/wp-plugin-development/scripts/detect_plugins.mjs +0 -122
  108. package/.github/skills/wp-plugin-directory-guidelines/SKILL.md +0 -133
  109. package/.github/skills/wp-plugin-directory-guidelines/references/gpl-compliance.md +0 -217
  110. package/.github/skills/wp-plugin-directory-guidelines/references/guideline-review-checklist.md +0 -592
  111. package/.github/skills/wp-plugin-directory-guidelines/references/naming-rules.md +0 -121
  112. package/.github/skills/wp-project-triage/SKILL.md +0 -39
  113. package/.github/skills/wp-project-triage/references/triage.schema.json +0 -143
  114. package/.github/skills/wp-project-triage/scripts/detect_wp_project.mjs +0 -610
  115. package/.github/skills/wp-rest-api/SKILL.md +0 -115
  116. package/.github/skills/wp-rest-api/references/authentication.md +0 -18
  117. package/.github/skills/wp-rest-api/references/custom-content-types.md +0 -20
  118. package/.github/skills/wp-rest-api/references/discovery-and-params.md +0 -20
  119. package/.github/skills/wp-rest-api/references/responses-and-fields.md +0 -30
  120. package/.github/skills/wp-rest-api/references/routes-and-endpoints.md +0 -36
  121. package/.github/skills/wp-rest-api/references/schema.md +0 -22
  122. package/.github/skills/wp-wpcli-and-ops/SKILL.md +0 -124
  123. package/.github/skills/wp-wpcli-and-ops/references/automation.md +0 -30
  124. package/.github/skills/wp-wpcli-and-ops/references/cron-and-cache.md +0 -23
  125. package/.github/skills/wp-wpcli-and-ops/references/debugging.md +0 -17
  126. package/.github/skills/wp-wpcli-and-ops/references/multisite.md +0 -22
  127. package/.github/skills/wp-wpcli-and-ops/references/packages-and-updates.md +0 -22
  128. package/.github/skills/wp-wpcli-and-ops/references/safety.md +0 -30
  129. package/.github/skills/wp-wpcli-and-ops/references/search-replace.md +0 -40
  130. package/.github/skills/wp-wpcli-and-ops/scripts/wpcli_inspect.mjs +0 -90
  131. package/.github/skills/wp-wpengine/SKILL.md +0 -127
  132. package/.github/skills/wpds/SKILL.md +0 -59
@@ -1,610 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import process from "node:process";
4
-
5
- const TOOL_VERSION = "0.1.0";
6
-
7
- const DEFAULT_IGNORES = new Set([
8
- ".git",
9
- "node_modules",
10
- "vendor",
11
- "dist",
12
- "build",
13
- "coverage",
14
- ".next",
15
- ".turbo",
16
- ]);
17
-
18
- function statSafe(p) {
19
- try {
20
- return fs.statSync(p);
21
- } catch {
22
- return null;
23
- }
24
- }
25
-
26
- function readFileSafe(p, maxBytes = 256 * 1024) {
27
- try {
28
- const buf = fs.readFileSync(p);
29
- if (buf.byteLength > maxBytes) return buf.subarray(0, maxBytes).toString("utf8");
30
- return buf.toString("utf8");
31
- } catch {
32
- return null;
33
- }
34
- }
35
-
36
- function scanForTokens(repoRoot, { tokens, exts, maxFiles = 2500, maxDepth = 8 }) {
37
- const loweredTokens = tokens.map((t) => t.toLowerCase());
38
- const matches = new Map();
39
-
40
- const { results: files, truncated } = findFilesRecursive(
41
- repoRoot,
42
- (p) => {
43
- const ext = path.extname(p).toLowerCase();
44
- return exts.includes(ext);
45
- },
46
- { maxFiles, maxDepth }
47
- );
48
-
49
- for (const filePath of files) {
50
- const contents = readFileSafe(filePath, 128 * 1024);
51
- if (!contents) continue;
52
- const haystack = contents.toLowerCase();
53
-
54
- for (let i = 0; i < loweredTokens.length; i += 1) {
55
- const token = loweredTokens[i];
56
- if (matches.has(token)) continue;
57
- if (haystack.includes(token)) matches.set(token, path.relative(repoRoot, filePath));
58
- }
59
- if (matches.size === loweredTokens.length) break;
60
- }
61
-
62
- return {
63
- truncated,
64
- matches: Object.fromEntries([...matches.entries()]),
65
- };
66
- }
67
-
68
- function existsFile(p) {
69
- const st = statSafe(p);
70
- return Boolean(st && st.isFile());
71
- }
72
-
73
- function existsDir(p) {
74
- const st = statSafe(p);
75
- return Boolean(st && st.isDirectory());
76
- }
77
-
78
- function detectPackageManager(repoRoot) {
79
- const hasPnpm = existsFile(path.join(repoRoot, "pnpm-lock.yaml"));
80
- const hasYarn = existsFile(path.join(repoRoot, "yarn.lock"));
81
- const hasNpm = existsFile(path.join(repoRoot, "package-lock.json"));
82
- const hasBun = existsFile(path.join(repoRoot, "bun.lockb")) || existsFile(path.join(repoRoot, "bun.lock"));
83
- if (hasPnpm) return "pnpm";
84
- if (hasYarn) return "yarn";
85
- if (hasBun) return "bun";
86
- if (hasNpm) return "npm";
87
- return null;
88
- }
89
-
90
- function findFilesRecursive(repoRoot, predicate, { maxFiles = 6000, maxDepth = 8 } = {}) {
91
- const results = [];
92
- const queue = [{ dir: repoRoot, depth: 0 }];
93
- let visited = 0;
94
-
95
- while (queue.length > 0) {
96
- const { dir, depth } = queue.shift();
97
- if (depth > maxDepth) continue;
98
-
99
- let entries;
100
- try {
101
- entries = fs.readdirSync(dir, { withFileTypes: true });
102
- } catch {
103
- continue;
104
- }
105
-
106
- for (const ent of entries) {
107
- const fullPath = path.join(dir, ent.name);
108
- if (ent.isDirectory()) {
109
- if (DEFAULT_IGNORES.has(ent.name)) continue;
110
- queue.push({ dir: fullPath, depth: depth + 1 });
111
- continue;
112
- }
113
- if (!ent.isFile()) continue;
114
-
115
- visited += 1;
116
- if (visited > maxFiles) return { results, truncated: true };
117
- if (predicate(fullPath)) results.push(fullPath);
118
- }
119
- }
120
-
121
- return { results, truncated: false };
122
- }
123
-
124
- function detectPluginHeaderFromPhpFile(filePath) {
125
- const contents = readFileSafe(filePath, 128 * 1024);
126
- if (!contents) return null;
127
- // Allow leading whitespace and asterisks common in block comments
128
- const headerMatch = contents.match(/^[ \\t*]*Plugin Name:\s*(.+)\s*$/im);
129
- if (!headerMatch) return null;
130
- return headerMatch[1].trim();
131
- }
132
-
133
- function detectThemeHeaderFromStyleCss(filePath) {
134
- const contents = readFileSafe(filePath, 128 * 1024);
135
- if (!contents) return null;
136
- // Allow leading whitespace and asterisks common in block comments
137
- const headerMatch = contents.match(/^[ \\t*]*Theme Name:\s*(.+)\s*$/im);
138
- if (!headerMatch) return null;
139
- return headerMatch[1].trim();
140
- }
141
-
142
- function guessWpCoreVersionFromCheckout(repoRoot) {
143
- const versionPhp = path.join(repoRoot, "wp-includes", "version.php");
144
- if (!existsFile(versionPhp)) return { value: null, source: null };
145
- const contents = readFileSafe(versionPhp, 64 * 1024);
146
- if (!contents) return { value: null, source: null };
147
- const match = contents.match(/\$wp_version\s*=\s*'([^']+)'/);
148
- if (!match) return { value: null, source: "wp-includes/version.php" };
149
- return { value: match[1], source: "wp-includes/version.php" };
150
- }
151
-
152
- function guessGutenbergVersion(repoRoot) {
153
- const gutenbergPackageJson = path.join(repoRoot, "packages", "plugins", "package.json");
154
- const rootPackageJson = path.join(repoRoot, "package.json");
155
-
156
- for (const candidate of [gutenbergPackageJson, rootPackageJson]) {
157
- if (!existsFile(candidate)) continue;
158
- const txt = readFileSafe(candidate);
159
- if (!txt) continue;
160
- try {
161
- const pkg = JSON.parse(txt);
162
- if (pkg?.name === "@wordpress/plugins" && typeof pkg?.version === "string") {
163
- return { value: pkg.version, source: path.relative(repoRoot, candidate) };
164
- }
165
- if (pkg?.name === "gutenberg" && typeof pkg?.version === "string") {
166
- return { value: pkg.version, source: path.relative(repoRoot, candidate) };
167
- }
168
- } catch {
169
- // ignore
170
- }
171
- }
172
- return { value: null, source: null };
173
- }
174
-
175
- function parsePackageJson(repoRoot) {
176
- const p = path.join(repoRoot, "package.json");
177
- if (!existsFile(p)) return null;
178
- const txt = readFileSafe(p);
179
- if (!txt) return null;
180
- try {
181
- return JSON.parse(txt);
182
- } catch {
183
- return null;
184
- }
185
- }
186
-
187
- function parseComposerJson(repoRoot) {
188
- const p = path.join(repoRoot, "composer.json");
189
- if (!existsFile(p)) return null;
190
- const txt = readFileSafe(p);
191
- if (!txt) return null;
192
- try {
193
- return JSON.parse(txt);
194
- } catch {
195
- return null;
196
- }
197
- }
198
-
199
- function detectConfigConstants(repoRoot) {
200
- const { results: configFiles } = findFilesRecursive(repoRoot, (p) => path.basename(p) === "wp-config.php", {
201
- maxFiles: 4000,
202
- maxDepth: 4,
203
- });
204
- const configPath = configFiles[0] ?? null;
205
- if (!configPath) {
206
- return { source: null, constants: {} };
207
- }
208
-
209
- const contents = readFileSafe(configPath, 256 * 1024);
210
- if (!contents) return { source: path.relative(repoRoot, configPath), constants: {} };
211
-
212
- const c = contents;
213
- const enabled = (name) =>
214
- new RegExp(`define\\(\\s*['"]${name}['"]\\s*,\\s*(true|1)\\s*\\)`, "i").test(c) ||
215
- new RegExp(`\\b${name}\\b\\s*=\\s*(true|1)`, "i").test(c);
216
-
217
- const mentioned = (name) => new RegExp(`\\b${name}\\b`, "i").test(c);
218
-
219
- return {
220
- source: path.relative(repoRoot, configPath),
221
- constants: {
222
- savequeriesMentioned: mentioned("SAVEQUERIES"),
223
- savequeriesEnabled: enabled("SAVEQUERIES"),
224
- wpDebugMentioned: mentioned("WP_DEBUG"),
225
- wpDebugEnabled: enabled("WP_DEBUG"),
226
- disableWpCronMentioned: mentioned("DISABLE_WP_CRON"),
227
- disableWpCronEnabled: enabled("DISABLE_WP_CRON"),
228
- },
229
- };
230
- }
231
-
232
- function detectKinds(repoRoot, signals) {
233
- const kinds = new Set();
234
-
235
- if (signals.isGutenbergRepo) kinds.add("gutenberg");
236
- if (signals.isWpCoreCheckout) kinds.add("wp-core");
237
- if (signals.hasWpContentDir) kinds.add("wp-site");
238
- if (signals.detectedThemeName) kinds.add(signals.isBlockTheme ? "wp-block-theme" : "wp-theme");
239
- if (signals.detectedPluginName) kinds.add(signals.isBlockPlugin ? "wp-block-plugin" : "wp-plugin");
240
- if (signals.hasMuPluginsDir) kinds.add("wp-mu-plugin");
241
-
242
- if (kinds.size === 0) kinds.add("unknown");
243
-
244
- const priority = [
245
- "gutenberg",
246
- "wp-core",
247
- "wp-site",
248
- "wp-block-theme",
249
- "wp-block-plugin",
250
- "wp-theme",
251
- "wp-mu-plugin",
252
- "wp-plugin",
253
- "unknown",
254
- ];
255
- let primary = "unknown";
256
- for (const k of priority) {
257
- if (kinds.has(k)) {
258
- primary = k;
259
- break;
260
- }
261
- }
262
-
263
- return { kind: [...kinds], primary };
264
- }
265
-
266
- function buildRecommendations({ repoRoot, primaryKind, packageManager, packageJson, composerJson, tooling, signals }) {
267
- const commands = [];
268
- const notes = [];
269
-
270
- if (tooling.node.hasPackageJson) {
271
- const pm = packageManager ?? "npm";
272
- const run = pm === "yarn" ? "yarn" : `${pm} run`;
273
- const hasScript = (name) => Boolean(packageJson?.scripts && Object.prototype.hasOwnProperty.call(packageJson.scripts, name));
274
- if (hasScript("lint")) commands.push(`${run} lint`);
275
- if (hasScript("test")) commands.push(`${run} test`);
276
- if (hasScript("build")) commands.push(`${run} build`);
277
- if (hasScript("start")) commands.push(`${run} start`);
278
- if (tooling.node.usesWordpressScripts) notes.push("Detected @wordpress/scripts usage; prefer its standard lint/build/test scripts.");
279
- }
280
-
281
- if (tooling.php.hasComposerJson) {
282
- commands.push("composer install");
283
- if (tooling.php.phpunitXml.length > 0) commands.push("vendor/bin/phpunit");
284
- }
285
-
286
- if (tooling.tests.hasWpEnv) notes.push("Detected wp-env; E2E workflows may rely on Docker.");
287
- if (signals.scanTruncated) notes.push("Scan truncated due to file limit; some signals may be missing.");
288
- if (primaryKind === "unknown") notes.push("Could not confidently classify repo; inspect root for plugin/theme headers or wp-content structure.");
289
-
290
- return { commands, notes };
291
- }
292
-
293
- function main() {
294
- const repoRoot = process.cwd();
295
-
296
- const wpContent = path.join(repoRoot, "wp-content");
297
- const pluginsDir = path.join(wpContent, "plugins");
298
- const muPluginsDir = path.join(wpContent, "mu-plugins");
299
- const themesDir = path.join(wpContent, "themes");
300
-
301
- const isWpCoreCheckout = existsFile(path.join(repoRoot, "wp-includes", "version.php"));
302
- const isGutenbergRepo =
303
- existsDir(path.join(repoRoot, "packages")) &&
304
- (existsDir(path.join(repoRoot, "packages", "block-editor")) || existsDir(path.join(repoRoot, "packages", "components")));
305
-
306
- const packageJson = parsePackageJson(repoRoot);
307
- const composerJson = parseComposerJson(repoRoot);
308
- const packageManager = detectPackageManager(repoRoot);
309
-
310
- const usesWordpressScripts = Boolean(
311
- packageJson?.devDependencies?.["@wordpress/scripts"] ||
312
- packageJson?.dependencies?.["@wordpress/scripts"] ||
313
- packageJson?.scripts?.build?.includes("wp-scripts") ||
314
- packageJson?.scripts?.start?.includes("wp-scripts") ||
315
- packageJson?.scripts?.test?.includes("wp-scripts") ||
316
- packageJson?.scripts?.lint?.includes("wp-scripts")
317
- );
318
-
319
- const pkgHasInteractivity = Boolean(
320
- packageJson?.devDependencies?.["@wordpress/interactivity"] || packageJson?.dependencies?.["@wordpress/interactivity"]
321
- );
322
- const pkgHasAbilities = Boolean(
323
- packageJson?.devDependencies?.["@wordpress/abilities"] || packageJson?.dependencies?.["@wordpress/abilities"]
324
- );
325
-
326
- const hasWpContentDir = existsDir(wpContent);
327
- const hasPluginsDir = existsDir(pluginsDir);
328
- const hasThemesDir = existsDir(themesDir);
329
- const hasMuPluginsDir = existsDir(muPluginsDir);
330
-
331
- const config = detectConfigConstants(repoRoot);
332
-
333
- const pluginCandidates = [];
334
- const themeCandidates = [];
335
-
336
- // Root-level plugin/theme detection (common when repo root is the plugin/theme).
337
- for (const entry of fs.readdirSync(repoRoot, { withFileTypes: true })) {
338
- if (!entry.isFile()) continue;
339
- if (entry.name.toLowerCase().endsWith(".php")) pluginCandidates.push(path.join(repoRoot, entry.name));
340
- if (entry.name === "style.css") themeCandidates.push(path.join(repoRoot, entry.name));
341
- }
342
-
343
- let detectedPluginName = null;
344
- for (const phpFile of pluginCandidates) {
345
- detectedPluginName = detectPluginHeaderFromPhpFile(phpFile);
346
- if (detectedPluginName) break;
347
- }
348
-
349
- let detectedThemeName = null;
350
- for (const styleCss of themeCandidates) {
351
- detectedThemeName = detectThemeHeaderFromStyleCss(styleCss);
352
- if (detectedThemeName) break;
353
- }
354
-
355
- const { results: blockJsonFiles, truncated: scanTruncated } = findFilesRecursive(
356
- repoRoot,
357
- (p) => path.basename(p) === "block.json",
358
- { maxFiles: 6000, maxDepth: 8 }
359
- );
360
- const { results: themeJsonFiles } = findFilesRecursive(repoRoot, (p) => path.basename(p) === "theme.json", {
361
- maxFiles: 6000,
362
- maxDepth: 8,
363
- });
364
-
365
- const templatesDirCandidates = [
366
- path.join(repoRoot, "templates"),
367
- path.join(repoRoot, "parts"),
368
- path.join(repoRoot, "patterns"),
369
- ];
370
-
371
- const isBlockTheme = themeJsonFiles.length > 0 && templatesDirCandidates.some((p) => existsDir(p));
372
- const isBlockPlugin = blockJsonFiles.length > 0;
373
-
374
- const interactivityScan = scanForTokens(repoRoot, {
375
- tokens: ["data-wp-interactive", "@wordpress/interactivity", "viewScriptModule"],
376
- exts: [".php", ".js", ".ts", ".tsx", ".json", ".html"],
377
- maxFiles: 2500,
378
- maxDepth: 8,
379
- });
380
-
381
- const abilitiesScan = scanForTokens(repoRoot, {
382
- tokens: [
383
- "wp_register_ability(",
384
- "wp_register_ability_category(",
385
- "wp_abilities_api_init",
386
- "wp_abilities_api_categories_init",
387
- "wp-abilities/v1",
388
- "@wordpress/abilities",
389
- ],
390
- exts: [".php", ".js", ".ts", ".tsx"],
391
- maxFiles: 2500,
392
- maxDepth: 8,
393
- });
394
-
395
- const innerBlocksScan = scanForTokens(repoRoot, {
396
- tokens: ["InnerBlocks", "useInnerBlocksProps", "InnerBlocks.Content"],
397
- exts: [".js", ".ts", ".tsx"],
398
- maxFiles: 2500,
399
- maxDepth: 8,
400
- });
401
-
402
- const restApiScan = scanForTokens(repoRoot, {
403
- tokens: ["register_rest_route", "register_rest_field", "WP_REST_Controller"],
404
- exts: [".php"],
405
- maxFiles: 2500,
406
- maxDepth: 8,
407
- });
408
-
409
- const wpCliConfigBasenames = new Set([
410
- "wp-cli.yml",
411
- "wp-cli.yaml",
412
- "wp-cli.local.yml",
413
- "wp-cli.local.yaml",
414
- ".wp-cli.yml",
415
- ".wp-cli.yaml",
416
- ]);
417
- const { results: wpCliConfigFiles, truncated: wpCliConfigTruncated } = findFilesRecursive(
418
- repoRoot,
419
- (p) => wpCliConfigBasenames.has(path.basename(p)),
420
- { maxFiles: 6000, maxDepth: 6 }
421
- );
422
-
423
- const composerRequire = composerJson?.require && typeof composerJson.require === "object" ? composerJson.require : {};
424
- const composerRequireDev =
425
- composerJson?.["require-dev"] && typeof composerJson["require-dev"] === "object" ? composerJson["require-dev"] : {};
426
- const composerHasWpCli = Boolean(
427
- composerRequire["wp-cli/wp-cli"] ||
428
- composerRequireDev["wp-cli/wp-cli"] ||
429
- composerRequire["wp-cli/wp-cli-bundle"] ||
430
- composerRequireDev["wp-cli/wp-cli-bundle"]
431
- );
432
-
433
- const wpCliTokenScan = scanForTokens(repoRoot, {
434
- tokens: [
435
- "wp search-replace",
436
- "wp db export",
437
- "wp db import",
438
- "wp cron event",
439
- "wp cache flush",
440
- "wp rewrite flush",
441
- "wp plugin update",
442
- "wp theme update",
443
- ],
444
- exts: [".sh", ".yml", ".yaml", ".js", ".ts", ".php", ".json"],
445
- maxFiles: 2500,
446
- maxDepth: 8,
447
- });
448
-
449
- const usesInteractivityApi = pkgHasInteractivity || Object.keys(interactivityScan.matches).length > 0;
450
- const usesAbilitiesApi = pkgHasAbilities || Object.keys(abilitiesScan.matches).length > 0;
451
- const usesRestApi = Object.keys(restApiScan.matches).length > 0;
452
- const usesInnerBlocks = Object.keys(innerBlocksScan.matches).length > 0;
453
- const usesWpCli = composerHasWpCli || wpCliConfigFiles.length > 0 || Object.keys(wpCliTokenScan.matches).length > 0;
454
-
455
- const wpContentRoot = path.join(repoRoot, "wp-content");
456
- const hasObjectCacheDropin = existsFile(path.join(wpContentRoot, "object-cache.php"));
457
- const hasAdvancedCacheDropin = existsFile(path.join(wpContentRoot, "advanced-cache.php"));
458
- const hasDbDropin = existsFile(path.join(wpContentRoot, "db.php"));
459
- const hasSunriseDropin = existsFile(path.join(wpContentRoot, "sunrise.php"));
460
- const hasQueryMonitorPlugin = existsDir(path.join(wpContentRoot, "plugins", "query-monitor"));
461
- const hasPerformanceLabPlugin = existsDir(path.join(wpContentRoot, "plugins", "performance-lab"));
462
-
463
- const phpunitXml = [];
464
- for (const candidate of ["phpunit.xml", "phpunit.xml.dist"]) {
465
- const full = path.join(repoRoot, candidate);
466
- if (existsFile(full)) phpunitXml.push(candidate);
467
- }
468
-
469
- const hasWpEnv =
470
- existsFile(path.join(repoRoot, ".wp-env.json")) ||
471
- existsFile(path.join(repoRoot, ".wp-env.override.json")) ||
472
- Boolean(packageJson?.devDependencies?.["@wordpress/env"] || packageJson?.dependencies?.["@wordpress/env"]);
473
-
474
- const hasPlaywright = Boolean(
475
- packageJson?.devDependencies?.["@playwright/test"] ||
476
- packageJson?.dependencies?.["@playwright/test"] ||
477
- packageJson?.devDependencies?.["@wordpress/e2e-test-utils-playwright"] ||
478
- packageJson?.dependencies?.["@wordpress/e2e-test-utils-playwright"]
479
- );
480
-
481
- const hasJest = Boolean(
482
- packageJson?.devDependencies?.jest ||
483
- packageJson?.dependencies?.jest ||
484
- packageJson?.devDependencies?.["@wordpress/jest-preset-default"] ||
485
- packageJson?.dependencies?.["@wordpress/jest-preset-default"]
486
- );
487
-
488
- const hasPhpUnit = phpunitXml.length > 0 || Boolean(composerJson?.requireDev?.phpunit || composerJson?.["require-dev"]?.phpunit);
489
-
490
- const hasPhpStan = existsFile(path.join(repoRoot, "phpstan.neon")) ||
491
- existsFile(path.join(repoRoot, "phpstan.neon.dist")) ||
492
- Boolean(composerJson?.requireDev?.["phpstan/phpstan"] || composerJson?.["require-dev"]?.["phpstan/phpstan"]);
493
-
494
- const signals = {
495
- paths: {
496
- repoRoot,
497
- wpContent: hasWpContentDir ? wpContent : null,
498
- pluginsDir: hasPluginsDir ? pluginsDir : null,
499
- themesDir: hasThemesDir ? themesDir : null,
500
- muPluginsDir: hasMuPluginsDir ? muPluginsDir : null,
501
- },
502
- isWpCoreCheckout,
503
- isGutenbergRepo,
504
- hasWpContentDir,
505
- hasPluginsDir,
506
- hasThemesDir,
507
- hasMuPluginsDir,
508
- detectedPluginName,
509
- detectedThemeName,
510
- isBlockPlugin,
511
- isBlockTheme,
512
- usesInteractivityApi,
513
- usesAbilitiesApi, usesRestApi, usesInnerBlocks,
514
- usesWpCli,
515
- performanceHints: {
516
- wpConfig: config.source,
517
- constants: config.constants,
518
- dropins: {
519
- objectCache: hasObjectCacheDropin,
520
- advancedCache: hasAdvancedCacheDropin,
521
- db: hasDbDropin,
522
- sunrise: hasSunriseDropin,
523
- },
524
- plugins: {
525
- queryMonitor: hasQueryMonitorPlugin,
526
- performanceLab: hasPerformanceLabPlugin,
527
- },
528
- },
529
- interactivityHints: {
530
- packageJson: pkgHasInteractivity,
531
- matches: interactivityScan.matches,
532
- scanTruncated: interactivityScan.truncated,
533
- },
534
- abilitiesHints: {
535
- packageJson: pkgHasAbilities,
536
- matches: abilitiesScan.matches,
537
- scanTruncated: abilitiesScan.truncated,
538
- },
539
- restApiHints: {
540
- matches: restApiScan.matches,
541
- scanTruncated: restApiScan.truncated,
542
- },
543
- innerBlocksHints: {
544
- matches: innerBlocksScan.matches,
545
- scanTruncated: innerBlocksScan.truncated,
546
- },
547
- wpCliHints: {
548
- configFiles: wpCliConfigFiles.map((p) => path.relative(repoRoot, p)).slice(0, 50),
549
- configScanTruncated: wpCliConfigTruncated,
550
- composerJson: composerHasWpCli,
551
- matches: wpCliTokenScan.matches,
552
- scanTruncated: wpCliTokenScan.truncated,
553
- },
554
- blockJsonFiles: blockJsonFiles.map((p) => path.relative(repoRoot, p)).slice(0, 50),
555
- themeJsonFiles: themeJsonFiles.map((p) => path.relative(repoRoot, p)).slice(0, 50),
556
- scanTruncated,
557
- };
558
-
559
- const { kind, primary } = detectKinds(repoRoot, signals);
560
-
561
- const versions = {
562
- wordpress: {
563
- core: guessWpCoreVersionFromCheckout(repoRoot),
564
- },
565
- gutenberg: guessGutenbergVersion(repoRoot),
566
- };
567
-
568
- const tooling = {
569
- php: {
570
- hasComposerJson: existsFile(path.join(repoRoot, "composer.json")),
571
- hasVendorDir: existsDir(path.join(repoRoot, "vendor")),
572
- hasPhpStan,
573
- phpunitXml,
574
- },
575
- node: {
576
- hasPackageJson: existsFile(path.join(repoRoot, "package.json")),
577
- packageManager,
578
- usesWordpressScripts,
579
- },
580
- tests: {
581
- hasPhpUnit,
582
- hasWpEnv,
583
- hasPlaywright,
584
- hasJest,
585
- },
586
- };
587
-
588
- const recommendations = buildRecommendations({
589
- repoRoot,
590
- primaryKind: primary,
591
- packageManager,
592
- packageJson,
593
- composerJson,
594
- tooling,
595
- signals,
596
- });
597
-
598
- const report = {
599
- tool: { name: "detect_wp_project", version: TOOL_VERSION },
600
- project: { kind, primary, notes: [] },
601
- signals,
602
- tooling,
603
- versions,
604
- recommendations,
605
- };
606
-
607
- process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
608
- }
609
-
610
- main();