wordpress-agent-kit 0.3.2 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/blueprint/SKILL.md +418 -0
- package/.agents/skills/wordpress-router/SKILL.md +52 -0
- package/.agents/skills/wordpress-router/references/decision-tree.md +55 -0
- package/.agents/skills/wp-abilities-api/SKILL.md +108 -0
- package/.agents/skills/wp-abilities-api/references/delegate-helper-pattern.md +241 -0
- package/.agents/skills/wp-abilities-api/references/domain-vs-projection.md +113 -0
- package/.agents/skills/wp-abilities-api/references/error-code-vocabulary.md +123 -0
- package/.agents/skills/wp-abilities-api/references/grouping-heuristic.md +89 -0
- package/.agents/skills/wp-abilities-api/references/input-schema-gotchas.md +265 -0
- package/.agents/skills/wp-abilities-api/references/php-registration.md +94 -0
- package/.agents/skills/wp-abilities-api/references/plugin-family-patterns.md +233 -0
- package/.agents/skills/wp-abilities-api/references/rest-api.md +13 -0
- package/.agents/skills/wp-abilities-api/references/shared-core-service.md +184 -0
- package/.agents/skills/wp-abilities-audit/SKILL.md +199 -0
- package/.agents/skills/wp-abilities-audit/references/audit-schema.md +300 -0
- package/.agents/skills/wp-abilities-audit/references/capability-gate-tracing.md +197 -0
- package/.agents/skills/wp-abilities-audit/references/controller-enumeration.md +116 -0
- package/.agents/skills/wp-abilities-verify/SKILL.md +215 -0
- package/.agents/skills/wp-abilities-verify/references/annotation-correctness.md +154 -0
- package/.agents/skills/wp-abilities-verify/references/audit-schema-validation.md +131 -0
- package/.agents/skills/wp-abilities-verify/references/permission-roundtrip.md +190 -0
- package/.agents/skills/wp-abilities-verify/references/runtime-harness.md +462 -0
- package/.agents/skills/wp-abilities-verify/references/schema-lints.md +118 -0
- package/.agents/skills/wp-abilities-verify/references/static-enumeration.md +126 -0
- package/.agents/skills/wp-block-development/SKILL.md +175 -0
- package/.agents/skills/wp-block-development/references/attributes-and-serialization.md +22 -0
- package/.agents/skills/wp-block-development/references/block-json.md +49 -0
- package/.agents/skills/wp-block-development/references/creating-new-blocks.md +46 -0
- package/.agents/skills/wp-block-development/references/debugging.md +36 -0
- package/.agents/skills/wp-block-development/references/deprecations.md +24 -0
- package/.agents/skills/wp-block-development/references/dynamic-rendering.md +23 -0
- package/.agents/skills/wp-block-development/references/inner-blocks.md +25 -0
- package/.agents/skills/wp-block-development/references/registration.md +30 -0
- package/.agents/skills/wp-block-development/references/supports-and-wrappers.md +18 -0
- package/.agents/skills/wp-block-development/references/tooling-and-testing.md +21 -0
- package/.agents/skills/wp-block-development/scripts/list_blocks.mjs +121 -0
- package/.agents/skills/wp-block-themes/SKILL.md +117 -0
- package/.agents/skills/wp-block-themes/references/creating-new-block-theme.md +37 -0
- package/.agents/skills/wp-block-themes/references/debugging.md +24 -0
- package/.agents/skills/wp-block-themes/references/patterns.md +18 -0
- package/.agents/skills/wp-block-themes/references/style-variations.md +14 -0
- package/.agents/skills/wp-block-themes/references/templates-and-parts.md +16 -0
- package/.agents/skills/wp-block-themes/references/theme-json.md +59 -0
- package/.agents/skills/wp-block-themes/scripts/detect_block_themes.mjs +117 -0
- package/.agents/skills/wp-interactivity-api/SKILL.md +180 -0
- package/.agents/skills/wp-interactivity-api/references/debugging.md +29 -0
- package/.agents/skills/wp-interactivity-api/references/directives-quickref.md +30 -0
- package/.agents/skills/wp-interactivity-api/references/server-side-rendering.md +310 -0
- package/.agents/skills/wp-performance/SKILL.md +147 -0
- package/.agents/skills/wp-performance/references/autoload-options.md +24 -0
- package/.agents/skills/wp-performance/references/cron.md +20 -0
- package/.agents/skills/wp-performance/references/database.md +20 -0
- package/.agents/skills/wp-performance/references/http-api.md +15 -0
- package/.agents/skills/wp-performance/references/measurement.md +21 -0
- package/.agents/skills/wp-performance/references/object-cache.md +24 -0
- package/.agents/skills/wp-performance/references/query-monitor-headless.md +38 -0
- package/.agents/skills/wp-performance/references/server-timing.md +22 -0
- package/.agents/skills/wp-performance/references/wp-cli-doctor.md +24 -0
- package/.agents/skills/wp-performance/references/wp-cli-profile.md +32 -0
- package/.agents/skills/wp-performance/scripts/perf_inspect.mjs +128 -0
- package/.agents/skills/wp-phpstan/SKILL.md +98 -0
- package/.agents/skills/wp-phpstan/references/configuration.md +52 -0
- package/.agents/skills/wp-phpstan/references/third-party-classes.md +76 -0
- package/.agents/skills/wp-phpstan/references/wordpress-annotations.md +124 -0
- package/.agents/skills/wp-phpstan/scripts/phpstan_inspect.mjs +263 -0
- package/.agents/skills/wp-playground/SKILL.md +233 -0
- package/.agents/skills/wp-playground/references/blueprints.md +36 -0
- package/.agents/skills/wp-playground/references/cli-commands.md +39 -0
- package/.agents/skills/wp-playground/references/debugging.md +16 -0
- package/.agents/skills/wp-playground/references/e2e-playwright.md +115 -0
- package/.agents/skills/wp-plugin-development/SKILL.md +113 -0
- package/.agents/skills/wp-plugin-development/references/data-and-cron.md +19 -0
- package/.agents/skills/wp-plugin-development/references/debugging.md +19 -0
- package/.agents/skills/wp-plugin-development/references/lifecycle.md +33 -0
- package/.agents/skills/wp-plugin-development/references/security.md +29 -0
- package/.agents/skills/wp-plugin-development/references/settings-api.md +22 -0
- package/.agents/skills/wp-plugin-development/references/structure.md +16 -0
- package/.agents/skills/wp-plugin-development/scripts/detect_plugins.mjs +122 -0
- package/.agents/skills/wp-plugin-directory-guidelines/SKILL.md +133 -0
- package/.agents/skills/wp-plugin-directory-guidelines/references/gpl-compliance.md +217 -0
- package/.agents/skills/wp-plugin-directory-guidelines/references/guideline-review-checklist.md +592 -0
- package/.agents/skills/wp-plugin-directory-guidelines/references/naming-rules.md +121 -0
- package/.agents/skills/wp-project-triage/SKILL.md +39 -0
- package/.agents/skills/wp-project-triage/references/triage.schema.json +143 -0
- package/.agents/skills/wp-project-triage/scripts/detect_wp_project.mjs +610 -0
- package/.agents/skills/wp-rest-api/SKILL.md +115 -0
- package/.agents/skills/wp-rest-api/references/authentication.md +18 -0
- package/.agents/skills/wp-rest-api/references/custom-content-types.md +20 -0
- package/.agents/skills/wp-rest-api/references/discovery-and-params.md +20 -0
- package/.agents/skills/wp-rest-api/references/responses-and-fields.md +30 -0
- package/.agents/skills/wp-rest-api/references/routes-and-endpoints.md +36 -0
- package/.agents/skills/wp-rest-api/references/schema.md +22 -0
- package/.agents/skills/wp-wpcli-and-ops/SKILL.md +126 -0
- package/.agents/skills/wp-wpcli-and-ops/references/automation.md +30 -0
- package/.agents/skills/wp-wpcli-and-ops/references/cron-and-cache.md +23 -0
- package/.agents/skills/wp-wpcli-and-ops/references/debugging.md +17 -0
- package/.agents/skills/wp-wpcli-and-ops/references/multisite.md +22 -0
- package/.agents/skills/wp-wpcli-and-ops/references/packages-and-updates.md +22 -0
- package/.agents/skills/wp-wpcli-and-ops/references/safety.md +30 -0
- package/.agents/skills/wp-wpcli-and-ops/references/search-replace.md +40 -0
- package/.agents/skills/wp-wpcli-and-ops/scripts/wpcli_inspect.mjs +90 -0
- package/.agents/skills/wp-wpengine/SKILL.md +398 -0
- package/.agents/skills/wp-wpengine/references/ci-gate.md +469 -0
- package/.agents/skills/wp-wpengine/references/github-actions-deploy.md +736 -0
- package/.agents/skills/wp-wpengine/scripts/ci-gate.sh +118 -0
- package/.agents/skills/wp-wpengine/scripts/wpe-check.sh +89 -0
- package/.agents/skills/wp-wpengine/scripts/wpe-preflight.sh +104 -0
- package/.agents/skills/wpds/SKILL.md +59 -0
- package/.github/agents/wp-architect.agent.md +1 -2
- package/.github/copilot-instructions.md +1 -1
- package/.github/instructions/wordpress-workflow.instructions.md +3 -3
- package/.github/skills/wp-playground/SKILL.md +132 -1
- package/.github/skills/wp-playground/references/e2e-playwright.md +115 -0
- package/.github/skills/wp-wpengine/SKILL.md +127 -0
- package/AGENTS.md +22 -10
- package/AGENTS.template.md +20 -10
- package/README.md +93 -86
- package/dist/cli.js +5 -1
- package/dist/commands/clean-skills.js +64 -0
- package/dist/commands/setup.js +6 -2
- package/dist/commands/sync-skills.js +3 -0
- package/dist/lib/api.js +176 -4
- package/dist/lib/installer.js +166 -2
- package/extensions/wp-agent-kit/index.ts +185 -10
- package/package.json +10 -14
- package/skills-custom/wp-wpengine/SKILL.md +398 -0
- package/skills-custom/wp-wpengine/references/ci-gate.md +469 -0
- package/skills-custom/wp-wpengine/references/github-actions-deploy.md +736 -0
- package/skills-custom/wp-wpengine/scripts/ci-gate.sh +118 -0
- package/skills-custom/wp-wpengine/scripts/wpe-check.sh +89 -0
- package/skills-custom/wp-wpengine/scripts/wpe-preflight.sh +104 -0
- package/.github/workflows/ci.yml +0 -44
- package/.husky/pre-commit +0 -7
- package/CLI_REVIEW.md +0 -250
- package/biome.json +0 -39
|
@@ -0,0 +1,610 @@
|
|
|
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();
|