@wp-typia/project-tools 0.22.2 → 0.22.4

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 (62) hide show
  1. package/dist/runtime/built-in-block-code-templates/interactivity.d.ts +1 -1
  2. package/dist/runtime/built-in-block-code-templates/interactivity.js +4 -2
  3. package/dist/runtime/cli-add-block-json.d.ts +31 -0
  4. package/dist/runtime/cli-add-block-json.js +65 -0
  5. package/dist/runtime/cli-add-collision.d.ts +129 -0
  6. package/dist/runtime/cli-add-collision.js +293 -0
  7. package/dist/runtime/cli-add-filesystem.d.ts +29 -0
  8. package/dist/runtime/cli-add-filesystem.js +77 -0
  9. package/dist/runtime/cli-add-help.d.ts +4 -0
  10. package/dist/runtime/cli-add-help.js +41 -0
  11. package/dist/runtime/cli-add-shared.d.ts +6 -255
  12. package/dist/runtime/cli-add-shared.js +6 -391
  13. package/dist/runtime/cli-add-types.d.ts +247 -0
  14. package/dist/runtime/cli-add-types.js +64 -0
  15. package/dist/runtime/cli-add-validation.d.ts +87 -0
  16. package/dist/runtime/cli-add-validation.js +147 -0
  17. package/dist/runtime/cli-add-workspace-ability-scaffold.d.ts +5 -0
  18. package/dist/runtime/cli-add-workspace-ability-scaffold.js +366 -0
  19. package/dist/runtime/cli-add-workspace-ability-templates.d.ts +34 -0
  20. package/dist/runtime/cli-add-workspace-ability-templates.js +500 -0
  21. package/dist/runtime/cli-add-workspace-ability-types.d.ts +27 -0
  22. package/dist/runtime/cli-add-workspace-ability-types.js +14 -0
  23. package/dist/runtime/cli-add-workspace-ability.js +12 -852
  24. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +35 -61
  25. package/dist/runtime/cli-add-workspace-ai-scaffold.d.ts +21 -0
  26. package/dist/runtime/cli-add-workspace-ai-scaffold.js +87 -0
  27. package/dist/runtime/cli-add-workspace-ai-templates.d.ts +4 -0
  28. package/dist/runtime/cli-add-workspace-ai-templates.js +607 -0
  29. package/dist/runtime/cli-add-workspace-ai.js +15 -688
  30. package/dist/runtime/cli-add-workspace-assets.js +7 -4
  31. package/dist/runtime/cli-add-workspace-mutation.d.ts +30 -0
  32. package/dist/runtime/cli-add-workspace-mutation.js +60 -0
  33. package/dist/runtime/cli-add-workspace.js +2 -98
  34. package/dist/runtime/cli-add.d.ts +2 -2
  35. package/dist/runtime/cli-add.js +2 -2
  36. package/dist/runtime/cli-doctor-workspace-bindings.d.ts +11 -0
  37. package/dist/runtime/cli-doctor-workspace-bindings.js +134 -0
  38. package/dist/runtime/cli-doctor-workspace-blocks.d.ts +11 -0
  39. package/dist/runtime/cli-doctor-workspace-blocks.js +439 -0
  40. package/dist/runtime/cli-doctor-workspace-features.d.ts +11 -0
  41. package/dist/runtime/cli-doctor-workspace-features.js +383 -0
  42. package/dist/runtime/cli-doctor-workspace-package.d.ts +18 -0
  43. package/dist/runtime/cli-doctor-workspace-package.js +59 -0
  44. package/dist/runtime/cli-doctor-workspace-shared.d.ts +69 -0
  45. package/dist/runtime/cli-doctor-workspace-shared.js +87 -0
  46. package/dist/runtime/cli-doctor-workspace.js +25 -1062
  47. package/dist/runtime/index.d.ts +2 -0
  48. package/dist/runtime/index.js +1 -0
  49. package/dist/runtime/migration-utils.d.ts +2 -1
  50. package/dist/runtime/migration-utils.js +3 -11
  51. package/dist/runtime/package-managers.d.ts +19 -0
  52. package/dist/runtime/package-managers.js +62 -0
  53. package/dist/runtime/template-source-cache.d.ts +59 -0
  54. package/dist/runtime/template-source-cache.js +160 -0
  55. package/dist/runtime/ts-source-masking.d.ts +28 -0
  56. package/dist/runtime/ts-source-masking.js +104 -0
  57. package/dist/runtime/typia-llm.d.ts +9 -1
  58. package/dist/runtime/typia-llm.js +20 -5
  59. package/dist/runtime/workspace-inventory.js +116 -59
  60. package/dist/runtime/workspace-project.d.ts +1 -1
  61. package/dist/runtime/workspace-project.js +2 -10
  62. package/package.json +4 -4
@@ -0,0 +1,383 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { EDITOR_PLUGIN_SLOT_IDS, REST_RESOURCE_METHOD_IDS, REST_RESOURCE_NAMESPACE_PATTERN, resolveEditorPluginSlotAlias, } from "./cli-add-shared.js";
4
+ import { checkExistingFiles, createDoctorCheck, WORKSPACE_ABILITY_EDITOR_ASSET, WORKSPACE_ABILITY_EDITOR_SCRIPT, WORKSPACE_ABILITY_GLOB, WORKSPACE_ADMIN_VIEW_ASSET, WORKSPACE_ADMIN_VIEW_GLOB, WORKSPACE_ADMIN_VIEW_SCRIPT, WORKSPACE_ADMIN_VIEW_STYLE, WORKSPACE_AI_FEATURE_GLOB, WORKSPACE_EDITOR_PLUGIN_EDITOR_ASSET, WORKSPACE_EDITOR_PLUGIN_EDITOR_SCRIPT, WORKSPACE_EDITOR_PLUGIN_EDITOR_STYLE, WORKSPACE_REST_RESOURCE_GLOB, } from "./cli-doctor-workspace-shared.js";
5
+ import { escapeRegex } from "./php-utils.js";
6
+ function getWorkspaceRestResourceRequiredFiles(restResource) {
7
+ const schemaNames = new Set();
8
+ if (restResource.methods.includes("list")) {
9
+ schemaNames.add("list-query");
10
+ schemaNames.add("list-response");
11
+ }
12
+ if (restResource.methods.includes("read")) {
13
+ schemaNames.add("read-query");
14
+ schemaNames.add("read-response");
15
+ }
16
+ if (restResource.methods.includes("create")) {
17
+ schemaNames.add("create-request");
18
+ schemaNames.add("create-response");
19
+ }
20
+ if (restResource.methods.includes("update")) {
21
+ schemaNames.add("update-query");
22
+ schemaNames.add("update-request");
23
+ schemaNames.add("update-response");
24
+ }
25
+ if (restResource.methods.includes("delete")) {
26
+ schemaNames.add("delete-query");
27
+ schemaNames.add("delete-response");
28
+ }
29
+ return Array.from(new Set([
30
+ restResource.apiFile,
31
+ ...Array.from(schemaNames, (schemaName) => path.join(path.dirname(restResource.typesFile), "api-schemas", `${schemaName}.schema.json`)),
32
+ restResource.clientFile,
33
+ restResource.dataFile,
34
+ restResource.openApiFile,
35
+ restResource.phpFile,
36
+ restResource.typesFile,
37
+ restResource.validatorsFile,
38
+ ]));
39
+ }
40
+ function checkWorkspaceRestResourceConfig(restResource) {
41
+ const hasNamespace = REST_RESOURCE_NAMESPACE_PATTERN.test(restResource.namespace);
42
+ const hasMethods = restResource.methods.length > 0 &&
43
+ restResource.methods.every((method) => REST_RESOURCE_METHOD_IDS.includes(method));
44
+ return createDoctorCheck(`REST resource config ${restResource.slug}`, hasNamespace && hasMethods ? "pass" : "fail", hasNamespace && hasMethods
45
+ ? `REST resource namespace ${restResource.namespace} with methods ${restResource.methods.join(", ")}`
46
+ : "REST resource namespace or methods are invalid");
47
+ }
48
+ function checkWorkspaceRestResourceBootstrap(projectDir, packageName, phpPrefix) {
49
+ const packageBaseName = packageName.split("/").pop() ?? packageName;
50
+ const bootstrapPath = path.join(projectDir, `${packageBaseName}.php`);
51
+ if (!fs.existsSync(bootstrapPath)) {
52
+ return createDoctorCheck("REST resource bootstrap", "fail", `Missing ${path.basename(bootstrapPath)}`);
53
+ }
54
+ const source = fs.readFileSync(bootstrapPath, "utf8");
55
+ const registerFunctionName = `${phpPrefix}_register_rest_resources`;
56
+ const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
57
+ const hasServerGlob = source.includes(WORKSPACE_REST_RESOURCE_GLOB);
58
+ const hasRegisterHook = source.includes(registerHook);
59
+ return createDoctorCheck("REST resource bootstrap", hasServerGlob && hasRegisterHook ? "pass" : "fail", hasServerGlob && hasRegisterHook
60
+ ? "REST resource PHP loader hook is present"
61
+ : "Missing REST resource PHP require glob or init hook");
62
+ }
63
+ function getWorkspaceAbilityRequiredFiles(ability) {
64
+ return Array.from(new Set([
65
+ ability.clientFile,
66
+ ability.configFile,
67
+ ability.dataFile,
68
+ ability.inputSchemaFile,
69
+ ability.outputSchemaFile,
70
+ ability.phpFile,
71
+ ability.typesFile,
72
+ ]));
73
+ }
74
+ function checkWorkspaceAbilityConfig(projectDir, ability) {
75
+ const configPath = path.join(projectDir, ability.configFile);
76
+ if (!fs.existsSync(configPath)) {
77
+ return createDoctorCheck(`Ability config ${ability.slug}`, "fail", `Missing ${ability.configFile}`);
78
+ }
79
+ try {
80
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
81
+ const abilityId = typeof config.abilityId === "string" ? config.abilityId.trim() : "";
82
+ const categorySlug = typeof config.category?.slug === "string"
83
+ ? config.category.slug.trim()
84
+ : "";
85
+ const hasValidAbilityId = /^[a-z0-9-]+\/[a-z0-9-]+$/u.test(abilityId);
86
+ const hasValidCategorySlug = /^[a-z0-9-]+$/u.test(categorySlug);
87
+ return createDoctorCheck(`Ability config ${ability.slug}`, hasValidAbilityId && hasValidCategorySlug ? "pass" : "fail", hasValidAbilityId && hasValidCategorySlug
88
+ ? `Ability id ${abilityId} in category ${categorySlug} is valid`
89
+ : "Ability config must define a valid abilityId (`namespace/ability-name`) and category.slug.");
90
+ }
91
+ catch (error) {
92
+ return createDoctorCheck(`Ability config ${ability.slug}`, "fail", error instanceof Error ? error.message : String(error));
93
+ }
94
+ }
95
+ function checkWorkspaceAbilityBootstrap(projectDir, packageName, phpPrefix) {
96
+ const packageBaseName = packageName.split("/").pop() ?? packageName;
97
+ const bootstrapPath = path.join(projectDir, `${packageBaseName}.php`);
98
+ if (!fs.existsSync(bootstrapPath)) {
99
+ return createDoctorCheck("Ability bootstrap", "fail", `Missing ${path.basename(bootstrapPath)}`);
100
+ }
101
+ const source = fs.readFileSync(bootstrapPath, "utf8");
102
+ const loadFunctionName = `${phpPrefix}_load_workflow_abilities`;
103
+ const enqueueFunctionName = `${phpPrefix}_enqueue_workflow_abilities`;
104
+ const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
105
+ const adminEnqueueHook = `add_action( 'admin_enqueue_scripts', '${enqueueFunctionName}' );`;
106
+ const editorEnqueueHook = `add_action( 'enqueue_block_editor_assets', '${enqueueFunctionName}' );`;
107
+ const hasLoaderHook = source.includes(loadHook);
108
+ const hasAdminEnqueueHook = source.includes(adminEnqueueHook);
109
+ const hasEditorEnqueueHook = source.includes(editorEnqueueHook);
110
+ const hasServerGlob = source.includes(WORKSPACE_ABILITY_GLOB);
111
+ const hasEditorScript = source.includes(WORKSPACE_ABILITY_EDITOR_SCRIPT);
112
+ const hasEditorAsset = source.includes(WORKSPACE_ABILITY_EDITOR_ASSET);
113
+ const hasScriptModuleEnqueue = source.includes("wp_enqueue_script_module");
114
+ return createDoctorCheck("Ability bootstrap", hasLoaderHook &&
115
+ hasAdminEnqueueHook &&
116
+ hasEditorEnqueueHook &&
117
+ hasServerGlob &&
118
+ hasEditorScript &&
119
+ hasEditorAsset &&
120
+ hasScriptModuleEnqueue
121
+ ? "pass"
122
+ : "fail", hasLoaderHook &&
123
+ hasAdminEnqueueHook &&
124
+ hasEditorEnqueueHook &&
125
+ hasServerGlob &&
126
+ hasEditorScript &&
127
+ hasEditorAsset &&
128
+ hasScriptModuleEnqueue
129
+ ? "Ability loader and admin/editor script-module bootstrap hooks are present"
130
+ : "Missing ability loader hook, script-module enqueue, or build/abilities asset references");
131
+ }
132
+ function checkWorkspaceAbilityIndex(projectDir, abilities) {
133
+ const indexRelativePath = [
134
+ path.join("src", "abilities", "index.ts"),
135
+ path.join("src", "abilities", "index.js"),
136
+ ].find((relativePath) => fs.existsSync(path.join(projectDir, relativePath)));
137
+ if (!indexRelativePath) {
138
+ return createDoctorCheck("Abilities index", "fail", "Missing src/abilities/index.ts or src/abilities/index.js");
139
+ }
140
+ const indexPath = path.join(projectDir, indexRelativePath);
141
+ const source = fs.readFileSync(indexPath, "utf8");
142
+ const missingExports = abilities.filter((ability) => {
143
+ const exportPattern = new RegExp(`^\\s*export\\s+(?:\\*\\s+from|\\{[^}]+\\}\\s+from)\\s+['"\`]\\./${escapeRegex(ability.slug)}\\/client['"\`]`, "mu");
144
+ return !exportPattern.test(source);
145
+ });
146
+ return createDoctorCheck("Abilities index", missingExports.length === 0 ? "pass" : "fail", missingExports.length === 0
147
+ ? "Ability client helpers are aggregated"
148
+ : `Missing ability exports for: ${missingExports.map((entry) => entry.slug).join(", ")}`);
149
+ }
150
+ function getWorkspaceAiFeatureRequiredFiles(aiFeature) {
151
+ return Array.from(new Set([
152
+ aiFeature.aiSchemaFile,
153
+ aiFeature.apiFile,
154
+ path.join(path.dirname(aiFeature.typesFile), "api-schemas", "feature-request.schema.json"),
155
+ path.join(path.dirname(aiFeature.typesFile), "api-schemas", "feature-response.schema.json"),
156
+ path.join(path.dirname(aiFeature.typesFile), "api-schemas", "feature-result.schema.json"),
157
+ aiFeature.clientFile,
158
+ aiFeature.dataFile,
159
+ aiFeature.openApiFile,
160
+ aiFeature.phpFile,
161
+ aiFeature.typesFile,
162
+ aiFeature.validatorsFile,
163
+ ]));
164
+ }
165
+ function checkWorkspaceAiFeatureConfig(aiFeature) {
166
+ const hasNamespace = REST_RESOURCE_NAMESPACE_PATTERN.test(aiFeature.namespace);
167
+ return createDoctorCheck(`AI feature config ${aiFeature.slug}`, hasNamespace ? "pass" : "fail", hasNamespace
168
+ ? `AI feature namespace ${aiFeature.namespace} is valid`
169
+ : "AI feature namespace is invalid");
170
+ }
171
+ function checkWorkspaceAiFeatureBootstrap(projectDir, packageName, phpPrefix) {
172
+ const packageBaseName = packageName.split("/").pop() ?? packageName;
173
+ const bootstrapPath = path.join(projectDir, `${packageBaseName}.php`);
174
+ if (!fs.existsSync(bootstrapPath)) {
175
+ return createDoctorCheck("AI feature bootstrap", "fail", `Missing ${path.basename(bootstrapPath)}`);
176
+ }
177
+ const source = fs.readFileSync(bootstrapPath, "utf8");
178
+ const registerFunctionName = `${phpPrefix}_register_ai_features`;
179
+ const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
180
+ const hasServerGlob = source.includes(WORKSPACE_AI_FEATURE_GLOB);
181
+ const hasRegisterHook = source.includes(registerHook);
182
+ return createDoctorCheck("AI feature bootstrap", hasServerGlob && hasRegisterHook ? "pass" : "fail", hasServerGlob && hasRegisterHook
183
+ ? "AI feature PHP loader hook is present"
184
+ : "Missing AI feature PHP require glob or init hook");
185
+ }
186
+ function getWorkspaceEditorPluginRequiredFiles(editorPlugin) {
187
+ const editorPluginDir = path.join("src", "editor-plugins", editorPlugin.slug);
188
+ const surfaceFile = editorPlugin.slot === "PluginSidebar"
189
+ ? path.join(editorPluginDir, "Sidebar.tsx")
190
+ : path.join(editorPluginDir, "Surface.tsx");
191
+ return Array.from(new Set([
192
+ editorPlugin.file,
193
+ surfaceFile,
194
+ path.join(editorPluginDir, "data.ts"),
195
+ path.join(editorPluginDir, "types.ts"),
196
+ path.join(editorPluginDir, "style.scss"),
197
+ ]));
198
+ }
199
+ function checkWorkspaceEditorPluginConfig(editorPlugin) {
200
+ const normalizedSlot = resolveEditorPluginSlotAlias(editorPlugin.slot);
201
+ const isValidSlot = Boolean(normalizedSlot);
202
+ return createDoctorCheck(`Editor plugin config ${editorPlugin.slug}`, isValidSlot ? "pass" : "fail", isValidSlot
203
+ ? `Editor plugin slot ${editorPlugin.slot} is supported as ${normalizedSlot}`
204
+ : `Unsupported editor plugin slot "${editorPlugin.slot}". Expected one of: ${EDITOR_PLUGIN_SLOT_IDS.join(", ")} or legacy aliases PluginSidebar, PluginDocumentSettingPanel.`);
205
+ }
206
+ function checkWorkspaceEditorPluginBootstrap(projectDir, packageName, phpPrefix) {
207
+ const packageBaseName = packageName.split("/").pop() ?? packageName;
208
+ const bootstrapPath = path.join(projectDir, `${packageBaseName}.php`);
209
+ if (!fs.existsSync(bootstrapPath)) {
210
+ return createDoctorCheck("Editor plugin bootstrap", "fail", `Missing ${path.basename(bootstrapPath)}`);
211
+ }
212
+ const source = fs.readFileSync(bootstrapPath, "utf8");
213
+ const enqueueFunctionName = `${phpPrefix}_enqueue_editor_plugins_editor`;
214
+ const enqueueHook = `add_action( 'enqueue_block_editor_assets', '${enqueueFunctionName}' );`;
215
+ const hasEditorEnqueueHook = source.includes(enqueueHook);
216
+ const hasEditorScript = source.includes(WORKSPACE_EDITOR_PLUGIN_EDITOR_SCRIPT);
217
+ const hasEditorAsset = source.includes(WORKSPACE_EDITOR_PLUGIN_EDITOR_ASSET);
218
+ const hasEditorStyle = source.includes(WORKSPACE_EDITOR_PLUGIN_EDITOR_STYLE);
219
+ return createDoctorCheck("Editor plugin bootstrap", hasEditorEnqueueHook && hasEditorScript && hasEditorAsset && hasEditorStyle ? "pass" : "fail", hasEditorEnqueueHook && hasEditorScript && hasEditorAsset && hasEditorStyle
220
+ ? "Editor plugin enqueue hook is present"
221
+ : "Missing editor plugin enqueue hook or build/editor-plugins script/style asset references");
222
+ }
223
+ function checkWorkspaceEditorPluginIndex(projectDir, editorPlugins) {
224
+ const indexRelativePath = [
225
+ path.join("src", "editor-plugins", "index.ts"),
226
+ path.join("src", "editor-plugins", "index.js"),
227
+ ].find((relativePath) => fs.existsSync(path.join(projectDir, relativePath)));
228
+ if (!indexRelativePath) {
229
+ return createDoctorCheck("Editor plugins index", "fail", "Missing src/editor-plugins/index.ts or src/editor-plugins/index.js");
230
+ }
231
+ const indexPath = path.join(projectDir, indexRelativePath);
232
+ const source = fs.readFileSync(indexPath, "utf8");
233
+ const missingImports = editorPlugins.filter((editorPlugin) => {
234
+ const importPattern = new RegExp(`['"\`]\\./${escapeRegex(editorPlugin.slug)}(?:/[^'"\`]*)?['"\`]`, "u");
235
+ return !importPattern.test(source);
236
+ });
237
+ return createDoctorCheck("Editor plugins index", missingImports.length === 0 ? "pass" : "fail", missingImports.length === 0
238
+ ? "Editor plugin registrations are aggregated"
239
+ : `Missing editor plugin imports for: ${missingImports
240
+ .map((entry) => entry.slug)
241
+ .join(", ")}`);
242
+ }
243
+ function getWorkspaceAdminViewRequiredFiles(adminView) {
244
+ const adminViewDir = path.join("src", "admin-views", adminView.slug);
245
+ return Array.from(new Set([
246
+ adminView.file,
247
+ adminView.phpFile,
248
+ path.join(adminViewDir, "Screen.tsx"),
249
+ path.join(adminViewDir, "config.ts"),
250
+ path.join(adminViewDir, "data.ts"),
251
+ path.join(adminViewDir, "style.scss"),
252
+ path.join(adminViewDir, "types.ts"),
253
+ ]));
254
+ }
255
+ function checkWorkspaceAdminViewConfig(adminView, inventory) {
256
+ if (adminView.source === undefined) {
257
+ return createDoctorCheck(`Admin view config ${adminView.slug}`, "pass", "Admin view uses a replaceable local fetcher");
258
+ }
259
+ const source = adminView.source.trim();
260
+ const restSourceMatch = /^rest-resource:([a-z][a-z0-9-]*)$/u.exec(source);
261
+ const coreDataSourceMatch = /^core-data:(postType|taxonomy)\/([a-z0-9][a-z0-9_-]*)$/u.exec(source);
262
+ const restResourceSlug = restSourceMatch?.[1];
263
+ const restResource = restResourceSlug
264
+ ? inventory.restResources.find((entry) => entry.slug === restResourceSlug)
265
+ : undefined;
266
+ const isValid = Boolean(restResource?.methods.includes("list")) || Boolean(coreDataSourceMatch);
267
+ return createDoctorCheck(`Admin view config ${adminView.slug}`, isValid ? "pass" : "fail", isValid
268
+ ? `Admin view source ${source} is list-capable`
269
+ : "Admin view source must use rest-resource:<slug> with a list-capable REST resource or core-data:<postType|taxonomy>/<name>");
270
+ }
271
+ function checkWorkspaceAdminViewBootstrap(projectDir, packageName, phpPrefix) {
272
+ const packageBaseName = packageName.split("/").pop() ?? packageName;
273
+ const bootstrapPath = path.join(projectDir, `${packageBaseName}.php`);
274
+ if (!fs.existsSync(bootstrapPath)) {
275
+ return createDoctorCheck("Admin view bootstrap", "fail", `Missing ${path.basename(bootstrapPath)}`);
276
+ }
277
+ const source = fs.readFileSync(bootstrapPath, "utf8");
278
+ const loadFunctionName = `${phpPrefix}_load_admin_views`;
279
+ const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
280
+ const hasLoaderHook = source.includes(loadHook);
281
+ const hasServerGlob = source.includes(WORKSPACE_ADMIN_VIEW_GLOB);
282
+ return createDoctorCheck("Admin view bootstrap", hasLoaderHook && hasServerGlob ? "pass" : "fail", hasLoaderHook && hasServerGlob
283
+ ? "Admin view PHP loader hook is present"
284
+ : "Missing admin view PHP require glob or plugins_loaded hook");
285
+ }
286
+ function checkWorkspaceAdminViewIndex(projectDir, adminViews) {
287
+ const indexRelativePath = [
288
+ path.join("src", "admin-views", "index.ts"),
289
+ path.join("src", "admin-views", "index.js"),
290
+ ].find((relativePath) => fs.existsSync(path.join(projectDir, relativePath)));
291
+ if (!indexRelativePath) {
292
+ return createDoctorCheck("Admin views index", "fail", "Missing src/admin-views/index.ts or src/admin-views/index.js");
293
+ }
294
+ const indexPath = path.join(projectDir, indexRelativePath);
295
+ const source = fs.readFileSync(indexPath, "utf8");
296
+ const missingImports = adminViews.filter((adminView) => {
297
+ const importPattern = new RegExp(`['"\`]\\./${escapeRegex(adminView.slug)}(?:/[^'"\`]*)?['"\`]`, "u");
298
+ return !importPattern.test(source);
299
+ });
300
+ return createDoctorCheck("Admin views index", missingImports.length === 0 ? "pass" : "fail", missingImports.length === 0
301
+ ? "Admin view registrations are aggregated"
302
+ : `Missing admin view imports for: ${missingImports
303
+ .map((entry) => entry.slug)
304
+ .join(", ")}`);
305
+ }
306
+ function checkWorkspaceAdminViewPhp(projectDir, adminView) {
307
+ const phpPath = path.join(projectDir, adminView.phpFile);
308
+ if (!fs.existsSync(phpPath)) {
309
+ return createDoctorCheck(`Admin view PHP ${adminView.slug}`, "fail", `Missing ${adminView.phpFile}`);
310
+ }
311
+ const source = fs.readFileSync(phpPath, "utf8");
312
+ const hasAdminMenu = source.includes("add_submenu_page");
313
+ const hasAdminEnqueue = source.includes("admin_enqueue_scripts");
314
+ const hasScript = source.includes(WORKSPACE_ADMIN_VIEW_SCRIPT);
315
+ const hasAsset = source.includes(WORKSPACE_ADMIN_VIEW_ASSET);
316
+ const hasStyle = source.includes(WORKSPACE_ADMIN_VIEW_STYLE);
317
+ const hasComponentsStyleDependency = source.includes("'wp-components'");
318
+ return createDoctorCheck(`Admin view PHP ${adminView.slug}`, hasAdminMenu &&
319
+ hasAdminEnqueue &&
320
+ hasScript &&
321
+ hasAsset &&
322
+ hasStyle &&
323
+ hasComponentsStyleDependency
324
+ ? "pass"
325
+ : "fail", hasAdminMenu &&
326
+ hasAdminEnqueue &&
327
+ hasScript &&
328
+ hasAsset &&
329
+ hasStyle &&
330
+ hasComponentsStyleDependency
331
+ ? "Admin menu, script, style, and wp-components style dependency are wired"
332
+ : "Missing admin menu, enqueue hook, build/admin-views asset reference, or wp-components style dependency");
333
+ }
334
+ /**
335
+ * Collect workspace doctor checks for REST resources, abilities, AI features, editor plugins, and admin views.
336
+ *
337
+ * @param workspace Resolved workspace metadata and filesystem paths.
338
+ * @param inventory Parsed workspace inventory from `scripts/block-config.ts`.
339
+ * @returns Ordered `DoctorCheck[]` rows for extracted workspace feature diagnostics.
340
+ */
341
+ export function getWorkspaceFeatureDoctorChecks(workspace, inventory) {
342
+ const checks = [];
343
+ if (inventory.restResources.length > 0) {
344
+ checks.push(checkWorkspaceRestResourceBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
345
+ }
346
+ for (const restResource of inventory.restResources) {
347
+ checks.push(checkWorkspaceRestResourceConfig(restResource));
348
+ checks.push(checkExistingFiles(workspace.projectDir, `REST resource ${restResource.slug}`, getWorkspaceRestResourceRequiredFiles(restResource)));
349
+ }
350
+ if (inventory.abilities.length > 0) {
351
+ checks.push(checkWorkspaceAbilityBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
352
+ checks.push(checkWorkspaceAbilityIndex(workspace.projectDir, inventory.abilities));
353
+ }
354
+ for (const ability of inventory.abilities) {
355
+ checks.push(checkWorkspaceAbilityConfig(workspace.projectDir, ability));
356
+ checks.push(checkExistingFiles(workspace.projectDir, `Ability ${ability.slug}`, getWorkspaceAbilityRequiredFiles(ability)));
357
+ }
358
+ if (inventory.aiFeatures.length > 0) {
359
+ checks.push(checkWorkspaceAiFeatureBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
360
+ }
361
+ for (const aiFeature of inventory.aiFeatures) {
362
+ checks.push(checkWorkspaceAiFeatureConfig(aiFeature));
363
+ checks.push(checkExistingFiles(workspace.projectDir, `AI feature ${aiFeature.slug}`, getWorkspaceAiFeatureRequiredFiles(aiFeature)));
364
+ }
365
+ if (inventory.editorPlugins.length > 0) {
366
+ checks.push(checkWorkspaceEditorPluginBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
367
+ checks.push(checkWorkspaceEditorPluginIndex(workspace.projectDir, inventory.editorPlugins));
368
+ }
369
+ for (const editorPlugin of inventory.editorPlugins) {
370
+ checks.push(checkExistingFiles(workspace.projectDir, `Editor plugin ${editorPlugin.slug}`, getWorkspaceEditorPluginRequiredFiles(editorPlugin)));
371
+ checks.push(checkWorkspaceEditorPluginConfig(editorPlugin));
372
+ }
373
+ if (inventory.adminViews.length > 0) {
374
+ checks.push(checkWorkspaceAdminViewBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
375
+ checks.push(checkWorkspaceAdminViewIndex(workspace.projectDir, inventory.adminViews));
376
+ }
377
+ for (const adminView of inventory.adminViews) {
378
+ checks.push(checkWorkspaceAdminViewConfig(adminView, inventory));
379
+ checks.push(checkExistingFiles(workspace.projectDir, `Admin view ${adminView.slug}`, getWorkspaceAdminViewRequiredFiles(adminView)));
380
+ checks.push(checkWorkspaceAdminViewPhp(workspace.projectDir, adminView));
381
+ }
382
+ return checks;
383
+ }
@@ -0,0 +1,18 @@
1
+ import type { DoctorCheck } from "./cli-doctor.js";
2
+ import type { WorkspacePackageJson, WorkspaceProject } from "./workspace-project.js";
3
+ /**
4
+ * Validate the package metadata that makes a project an official workspace.
5
+ *
6
+ * @param workspace Resolved workspace metadata and filesystem paths.
7
+ * @param packageJson Parsed workspace package manifest.
8
+ * @returns A `DoctorCheck` describing whether package metadata matches the workspace contract.
9
+ */
10
+ export declare function getWorkspacePackageMetadataCheck(workspace: WorkspaceProject, packageJson: WorkspacePackageJson): DoctorCheck;
11
+ /**
12
+ * Report whether a workspace configured for migrations exposes the expected doctor inputs.
13
+ *
14
+ * @param workspace Resolved workspace metadata and filesystem paths.
15
+ * @param packageJson Parsed workspace package manifest.
16
+ * @returns A migration hint row when the workspace uses migrations, otherwise `null`.
17
+ */
18
+ export declare function getMigrationWorkspaceHintCheck(workspace: WorkspaceProject, packageJson: WorkspacePackageJson): DoctorCheck | null;
@@ -0,0 +1,59 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createDoctorCheck, getWorkspaceBootstrapRelativePath, } from "./cli-doctor-workspace-shared.js";
4
+ import { WORKSPACE_TEMPLATE_PACKAGE } from "./workspace-project.js";
5
+ /**
6
+ * Validate the package metadata that makes a project an official workspace.
7
+ *
8
+ * @param workspace Resolved workspace metadata and filesystem paths.
9
+ * @param packageJson Parsed workspace package manifest.
10
+ * @returns A `DoctorCheck` describing whether package metadata matches the workspace contract.
11
+ */
12
+ export function getWorkspacePackageMetadataCheck(workspace, packageJson) {
13
+ const issues = [];
14
+ const packageName = packageJson.name;
15
+ const bootstrapRelativePath = getWorkspaceBootstrapRelativePath(typeof packageName === "string" && packageName.length > 0 ? packageName : workspace.packageName);
16
+ const wpTypia = packageJson.wpTypia;
17
+ if (typeof packageName !== "string" || packageName.length === 0) {
18
+ issues.push("package.json must define a string name for workspace bootstrap resolution");
19
+ }
20
+ if (wpTypia?.projectType !== "workspace") {
21
+ issues.push('wpTypia.projectType must be "workspace"');
22
+ }
23
+ if (wpTypia?.templatePackage !== WORKSPACE_TEMPLATE_PACKAGE) {
24
+ issues.push(`wpTypia.templatePackage must be "${WORKSPACE_TEMPLATE_PACKAGE}"`);
25
+ }
26
+ if (wpTypia?.namespace !== workspace.workspace.namespace) {
27
+ issues.push(`wpTypia.namespace must equal "${workspace.workspace.namespace}"`);
28
+ }
29
+ if (wpTypia?.textDomain !== workspace.workspace.textDomain) {
30
+ issues.push(`wpTypia.textDomain must equal "${workspace.workspace.textDomain}"`);
31
+ }
32
+ if (wpTypia?.phpPrefix !== workspace.workspace.phpPrefix) {
33
+ issues.push(`wpTypia.phpPrefix must equal "${workspace.workspace.phpPrefix}"`);
34
+ }
35
+ if (!fs.existsSync(path.join(workspace.projectDir, bootstrapRelativePath))) {
36
+ issues.push(`Missing bootstrap file ${bootstrapRelativePath}`);
37
+ }
38
+ return createDoctorCheck("Workspace package metadata", issues.length === 0 ? "pass" : "fail", issues.length === 0
39
+ ? `package.json metadata aligns with ${workspace.packageName} and ${bootstrapRelativePath}`
40
+ : issues.join("; "));
41
+ }
42
+ /**
43
+ * Report whether a workspace configured for migrations exposes the expected doctor inputs.
44
+ *
45
+ * @param workspace Resolved workspace metadata and filesystem paths.
46
+ * @param packageJson Parsed workspace package manifest.
47
+ * @returns A migration hint row when the workspace uses migrations, otherwise `null`.
48
+ */
49
+ export function getMigrationWorkspaceHintCheck(workspace, packageJson) {
50
+ const hasMigrationScript = typeof packageJson.scripts?.["migration:doctor"] === "string";
51
+ const migrationConfigRelativePath = path.join("src", "migrations", "config.ts");
52
+ const hasMigrationConfig = fs.existsSync(path.join(workspace.projectDir, migrationConfigRelativePath));
53
+ if (!hasMigrationScript && !hasMigrationConfig) {
54
+ return null;
55
+ }
56
+ return createDoctorCheck("Migration workspace", hasMigrationConfig ? "pass" : "fail", hasMigrationConfig
57
+ ? "Run `wp-typia migrate doctor --all` for migration target, snapshot, fixture, and generated artifact checks"
58
+ : `Missing ${migrationConfigRelativePath} for the configured migration workspace`);
59
+ }
@@ -0,0 +1,69 @@
1
+ import type { DoctorCheck } from "./cli-doctor.js";
2
+ /** Glob pattern for generated binding-source PHP entrypoints. */
3
+ export declare const WORKSPACE_BINDING_SERVER_GLOB = "/src/bindings/*/server.php";
4
+ /** Relative path to the generated binding editor bundle. */
5
+ export declare const WORKSPACE_BINDING_EDITOR_SCRIPT = "build/bindings/index.js";
6
+ /** Relative path to the generated binding asset manifest. */
7
+ export declare const WORKSPACE_BINDING_EDITOR_ASSET = "build/bindings/index.asset.php";
8
+ /** Glob pattern for generated REST resource PHP entrypoints. */
9
+ export declare const WORKSPACE_REST_RESOURCE_GLOB = "/inc/rest/*.php";
10
+ /** Glob pattern for generated ability PHP entrypoints. */
11
+ export declare const WORKSPACE_ABILITY_GLOB = "/inc/abilities/*.php";
12
+ /** Relative path to the generated ability editor bundle. */
13
+ export declare const WORKSPACE_ABILITY_EDITOR_SCRIPT = "build/abilities/index.js";
14
+ /** Relative path to the generated ability asset manifest. */
15
+ export declare const WORKSPACE_ABILITY_EDITOR_ASSET = "build/abilities/index.asset.php";
16
+ /** Glob pattern for generated AI feature PHP entrypoints. */
17
+ export declare const WORKSPACE_AI_FEATURE_GLOB = "/inc/ai-features/*.php";
18
+ /** Glob pattern for generated admin view PHP entrypoints. */
19
+ export declare const WORKSPACE_ADMIN_VIEW_GLOB = "/inc/admin-views/*.php";
20
+ /** Relative path to the generated admin view editor bundle. */
21
+ export declare const WORKSPACE_ADMIN_VIEW_SCRIPT = "build/admin-views/index.js";
22
+ /** Relative path to the generated admin view asset manifest. */
23
+ export declare const WORKSPACE_ADMIN_VIEW_ASSET = "build/admin-views/index.asset.php";
24
+ /** Relative path to the generated admin view stylesheet. */
25
+ export declare const WORKSPACE_ADMIN_VIEW_STYLE = "build/admin-views/style-index.css";
26
+ /** Relative path to the generated editor plugin bundle. */
27
+ export declare const WORKSPACE_EDITOR_PLUGIN_EDITOR_SCRIPT = "build/editor-plugins/index.js";
28
+ /** Relative path to the generated editor plugin asset manifest. */
29
+ export declare const WORKSPACE_EDITOR_PLUGIN_EDITOR_ASSET = "build/editor-plugins/index.asset.php";
30
+ /** Relative path to the generated editor plugin stylesheet. */
31
+ export declare const WORKSPACE_EDITOR_PLUGIN_EDITOR_STYLE = "build/editor-plugins/style-index.css";
32
+ /** Canonical generated artifact filenames expected in each workspace block directory. */
33
+ export declare const WORKSPACE_GENERATED_BLOCK_ARTIFACTS: readonly ["block.json", "typia.manifest.json", "typia.schema.json", "typia-validator.php", "typia.openapi.json"];
34
+ /** Pattern for full block names in `namespace/slug` format. */
35
+ export declare const WORKSPACE_FULL_BLOCK_NAME_PATTERN: RegExp;
36
+ /**
37
+ * Create a doctor result row with an optional stable diagnostic code.
38
+ *
39
+ * @param label Human-readable doctor row label.
40
+ * @param status Pass, warn, or fail status for the row.
41
+ * @param detail Detailed remediation or success text for CLI output.
42
+ * @param code Optional stable machine-readable diagnostic code.
43
+ * @returns A normalized `DoctorCheck` object for CLI rendering.
44
+ */
45
+ export declare function createDoctorCheck(label: string, status: DoctorCheck["status"], detail: string, code?: string): DoctorCheck;
46
+ /**
47
+ * Create the standard workspace-doctor scope row.
48
+ *
49
+ * @param status Pass or fail scope status for the doctor run.
50
+ * @param detail Scope summary describing what diagnostics ran.
51
+ * @returns A `DoctorCheck` row labelled `Doctor scope`.
52
+ */
53
+ export declare function createDoctorScopeCheck(status: DoctorCheck["status"], detail: string): DoctorCheck;
54
+ /**
55
+ * Resolve the expected workspace bootstrap file from a package name.
56
+ *
57
+ * @param packageName Package name used to derive the plugin bootstrap basename.
58
+ * @returns Relative PHP bootstrap filename for the workspace root.
59
+ */
60
+ export declare function getWorkspaceBootstrapRelativePath(packageName: string): string;
61
+ /**
62
+ * Verify that every referenced relative file exists inside a workspace.
63
+ *
64
+ * @param projectDir Absolute workspace root directory.
65
+ * @param label Doctor row label for the file set being checked.
66
+ * @param filePaths Relative file paths to validate.
67
+ * @returns A passing or failing `DoctorCheck` describing any missing files.
68
+ */
69
+ export declare function checkExistingFiles(projectDir: string, label: string, filePaths: Array<string | undefined>): DoctorCheck;
@@ -0,0 +1,87 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ /** Glob pattern for generated binding-source PHP entrypoints. */
4
+ export const WORKSPACE_BINDING_SERVER_GLOB = "/src/bindings/*/server.php";
5
+ /** Relative path to the generated binding editor bundle. */
6
+ export const WORKSPACE_BINDING_EDITOR_SCRIPT = "build/bindings/index.js";
7
+ /** Relative path to the generated binding asset manifest. */
8
+ export const WORKSPACE_BINDING_EDITOR_ASSET = "build/bindings/index.asset.php";
9
+ /** Glob pattern for generated REST resource PHP entrypoints. */
10
+ export const WORKSPACE_REST_RESOURCE_GLOB = "/inc/rest/*.php";
11
+ /** Glob pattern for generated ability PHP entrypoints. */
12
+ export const WORKSPACE_ABILITY_GLOB = "/inc/abilities/*.php";
13
+ /** Relative path to the generated ability editor bundle. */
14
+ export const WORKSPACE_ABILITY_EDITOR_SCRIPT = "build/abilities/index.js";
15
+ /** Relative path to the generated ability asset manifest. */
16
+ export const WORKSPACE_ABILITY_EDITOR_ASSET = "build/abilities/index.asset.php";
17
+ /** Glob pattern for generated AI feature PHP entrypoints. */
18
+ export const WORKSPACE_AI_FEATURE_GLOB = "/inc/ai-features/*.php";
19
+ /** Glob pattern for generated admin view PHP entrypoints. */
20
+ export const WORKSPACE_ADMIN_VIEW_GLOB = "/inc/admin-views/*.php";
21
+ /** Relative path to the generated admin view editor bundle. */
22
+ export const WORKSPACE_ADMIN_VIEW_SCRIPT = "build/admin-views/index.js";
23
+ /** Relative path to the generated admin view asset manifest. */
24
+ export const WORKSPACE_ADMIN_VIEW_ASSET = "build/admin-views/index.asset.php";
25
+ /** Relative path to the generated admin view stylesheet. */
26
+ export const WORKSPACE_ADMIN_VIEW_STYLE = "build/admin-views/style-index.css";
27
+ /** Relative path to the generated editor plugin bundle. */
28
+ export const WORKSPACE_EDITOR_PLUGIN_EDITOR_SCRIPT = "build/editor-plugins/index.js";
29
+ /** Relative path to the generated editor plugin asset manifest. */
30
+ export const WORKSPACE_EDITOR_PLUGIN_EDITOR_ASSET = "build/editor-plugins/index.asset.php";
31
+ /** Relative path to the generated editor plugin stylesheet. */
32
+ export const WORKSPACE_EDITOR_PLUGIN_EDITOR_STYLE = "build/editor-plugins/style-index.css";
33
+ /** Canonical generated artifact filenames expected in each workspace block directory. */
34
+ export const WORKSPACE_GENERATED_BLOCK_ARTIFACTS = [
35
+ "block.json",
36
+ "typia.manifest.json",
37
+ "typia.schema.json",
38
+ "typia-validator.php",
39
+ "typia.openapi.json",
40
+ ];
41
+ /** Pattern for full block names in `namespace/slug` format. */
42
+ export const WORKSPACE_FULL_BLOCK_NAME_PATTERN = /^[a-z0-9-]+\/[a-z0-9-]+$/u;
43
+ /**
44
+ * Create a doctor result row with an optional stable diagnostic code.
45
+ *
46
+ * @param label Human-readable doctor row label.
47
+ * @param status Pass, warn, or fail status for the row.
48
+ * @param detail Detailed remediation or success text for CLI output.
49
+ * @param code Optional stable machine-readable diagnostic code.
50
+ * @returns A normalized `DoctorCheck` object for CLI rendering.
51
+ */
52
+ export function createDoctorCheck(label, status, detail, code) {
53
+ return code ? { code, detail, label, status } : { detail, label, status };
54
+ }
55
+ /**
56
+ * Create the standard workspace-doctor scope row.
57
+ *
58
+ * @param status Pass or fail scope status for the doctor run.
59
+ * @param detail Scope summary describing what diagnostics ran.
60
+ * @returns A `DoctorCheck` row labelled `Doctor scope`.
61
+ */
62
+ export function createDoctorScopeCheck(status, detail) {
63
+ return createDoctorCheck("Doctor scope", status, detail);
64
+ }
65
+ /**
66
+ * Resolve the expected workspace bootstrap file from a package name.
67
+ *
68
+ * @param packageName Package name used to derive the plugin bootstrap basename.
69
+ * @returns Relative PHP bootstrap filename for the workspace root.
70
+ */
71
+ export function getWorkspaceBootstrapRelativePath(packageName) {
72
+ return `${packageName.split("/").pop() ?? packageName}.php`;
73
+ }
74
+ /**
75
+ * Verify that every referenced relative file exists inside a workspace.
76
+ *
77
+ * @param projectDir Absolute workspace root directory.
78
+ * @param label Doctor row label for the file set being checked.
79
+ * @param filePaths Relative file paths to validate.
80
+ * @returns A passing or failing `DoctorCheck` describing any missing files.
81
+ */
82
+ export function checkExistingFiles(projectDir, label, filePaths) {
83
+ const missing = filePaths
84
+ .filter((filePath) => typeof filePath === "string")
85
+ .filter((filePath) => !fs.existsSync(path.join(projectDir, filePath)));
86
+ return createDoctorCheck(label, missing.length === 0 ? "pass" : "fail", missing.length === 0 ? "All referenced files exist" : `Missing: ${missing.join(", ")}`);
87
+ }