wp-typia 0.23.0 → 0.24.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 (34) hide show
  1. package/README.md +3 -1
  2. package/bin/routing-metadata.generated.js +11 -0
  3. package/dist-bunli/.bunli/commands.gen.js +11884 -9017
  4. package/dist-bunli/{cli-9npd9was.js → cli-0v407aag.js} +12 -10
  5. package/dist-bunli/{cli-hhp1d348.js → cli-1170yyve.js} +8 -7
  6. package/dist-bunli/{cli-1meywwsy.js → cli-74y6z3yx.js} +1455 -819
  7. package/dist-bunli/{cli-qse6myha.js → cli-8hxf9qw6.js} +11 -3
  8. package/dist-bunli/{cli-8reep89s.js → cli-9fx0qgb7.js} +2 -2
  9. package/dist-bunli/{cli-add-21bvpfgw.js → cli-add-nmdraf20.js} +8542 -7667
  10. package/dist-bunli/{cli-52ke0ptp.js → cli-am5x7tb4.js} +8 -2
  11. package/dist-bunli/{cli-43mx1vfb.js → cli-bajwv85z.js} +2 -1
  12. package/dist-bunli/cli-ccax7s0s.js +34 -0
  13. package/dist-bunli/{cli-z5qkx2pn.js → cli-cwjdzq6n.js} +79 -13
  14. package/dist-bunli/{cli-diagnostics-5dvztm7q.js → cli-diagnostics-10drxh34.js} +1 -1
  15. package/dist-bunli/{cli-doctor-wy2yjsge.js → cli-doctor-pcss6ecx.js} +688 -459
  16. package/dist-bunli/{cli-2rqf6t0b.js → cli-e4bwd81c.js} +8 -11
  17. package/dist-bunli/{cli-init-xnsbxncv.js → cli-init-he7vm7kc.js} +15 -11
  18. package/dist-bunli/{cli-prompt-614tq57c.js → cli-prompt-ncyg68rn.js} +1 -1
  19. package/dist-bunli/{cli-bq2v559b.js → cli-rdcga1bd.js} +31 -13
  20. package/dist-bunli/{cli-scaffold-zhp2ym8z.js → cli-scaffold-an2k0fnm.js} +28 -16
  21. package/dist-bunli/{cli-c2acv5dv.js → cli-sw06c521.js} +2 -2
  22. package/dist-bunli/{cli-templates-hc71dfc2.js → cli-templates-g8t4fm11.js} +3 -2
  23. package/dist-bunli/{cli-p95wr1q8.js → cli-tq730sqt.js} +6 -3
  24. package/dist-bunli/{cli-ts9thts5.js → cli-v0nnagb3.js} +1513 -1053
  25. package/dist-bunli/{cli-agywa5n6.js → cli-y0a8nztv.js} +15 -6
  26. package/dist-bunli/cli-z48frc8t.js +229 -0
  27. package/dist-bunli/cli.js +5 -5
  28. package/dist-bunli/{command-list-aqrkx021.js → command-list-xaw5agks.js} +241 -64
  29. package/dist-bunli/{create-template-validation-rtec5sng.js → create-template-validation-4fr851vg.js} +5 -4
  30. package/dist-bunli/{migrations-bx0yvc2v.js → migrations-z7f4kxba.js} +10 -9
  31. package/dist-bunli/node-cli.js +661 -389
  32. package/dist-bunli/{workspace-project-csnnggz6.js → workspace-project-gmv2a71z.js} +4 -3
  33. package/package.json +2 -2
  34. package/dist-bunli/cli-j8et6jvr.js +0 -123
@@ -1,19 +1,21 @@
1
1
  // @bun
2
2
  import {
3
+ assertPostMetaBindingPath,
3
4
  hasAdminViewManualSettingsRouteParameters,
4
5
  hasExecutablePattern,
5
6
  hasUncommentedPattern,
6
7
  isAdminViewManualSettingsRestResource,
8
+ loadPostMetaBindingFieldsSync,
7
9
  maskTypeScriptCommentsAndLiterals
8
- } from "./cli-j8et6jvr.js";
10
+ } from "./cli-z48frc8t.js";
9
11
  import {
10
12
  getBuiltInTemplateLayerDirs,
11
13
  isOmittableBuiltInTemplateLayerDir
12
- } from "./cli-c2acv5dv.js";
14
+ } from "./cli-sw06c521.js";
13
15
  import {
14
16
  isBuiltInTemplateId,
15
17
  listTemplates
16
- } from "./cli-qse6myha.js";
18
+ } from "./cli-8hxf9qw6.js";
17
19
  import {
18
20
  EDITOR_PLUGIN_SLOT_IDS,
19
21
  HOOKED_BLOCK_ANCHOR_PATTERN,
@@ -24,28 +26,33 @@ import {
24
26
  REST_RESOURCE_NAMESPACE_PATTERN,
25
27
  assertValidPostMetaPostType,
26
28
  escapeRegex,
29
+ formatPatternCatalogDiagnostics,
27
30
  isGeneratedRestResourceRoutePatternCompatible,
28
31
  pathExists,
29
32
  readWorkspaceInventoryAsync,
30
- resolveEditorPluginSlotAlias
31
- } from "./cli-ts9thts5.js";
33
+ resolveEditorPluginSlotAlias,
34
+ resolvePatternCatalogContentFile,
35
+ validatePatternCatalog
36
+ } from "./cli-v0nnagb3.js";
32
37
  import"./cli-cvxvcw7c.js";
33
38
  import"./cli-t73q5aqz.js";
34
- import"./cli-43mx1vfb.js";
39
+ import"./cli-bajwv85z.js";
35
40
  import {
36
41
  CLI_DIAGNOSTIC_CODES,
37
42
  createCliCommandError,
38
43
  formatDoctorCheckLine,
39
- formatDoctorSummaryLine,
40
- getDoctorFailureDetailLines
41
- } from "./cli-p95wr1q8.js";
44
+ formatDoctorSummaryLine
45
+ } from "./cli-tq730sqt.js";
42
46
  import {
43
47
  WORKSPACE_TEMPLATE_PACKAGE,
44
48
  getInvalidWorkspaceProjectReason,
45
49
  parseWorkspacePackageJson,
46
50
  tryResolveWorkspaceProject
47
- } from "./cli-hhp1d348.js";
48
- import"./cli-52ke0ptp.js";
51
+ } from "./cli-1170yyve.js";
52
+ import"./cli-am5x7tb4.js";
53
+ import {
54
+ readJsonFileSync
55
+ } from "./cli-ccax7s0s.js";
49
56
  import"./cli-xnn9xjcy.js";
50
57
 
51
58
  // ../wp-typia-project-tools/src/runtime/cli-doctor-environment.ts
@@ -229,7 +236,9 @@ function checkWorkspaceBindingTarget(projectDir, workspace, registeredBlockSlugs
229
236
  const blockJsonPath = path3.join(projectDir, blockJsonRelativePath);
230
237
  const issues = [];
231
238
  try {
232
- const blockJson = parseScaffoldBlockMetadata(JSON.parse(fs3.readFileSync(blockJsonPath, "utf8")));
239
+ const blockJson = parseScaffoldBlockMetadata(readJsonFileSync(blockJsonPath, {
240
+ context: "workspace block metadata"
241
+ }));
233
242
  const attributes = blockJson.attributes;
234
243
  if (!attributes || typeof attributes !== "object" || Array.isArray(attributes)) {
235
244
  issues.push(`${blockJsonRelativePath} must define an attributes object`);
@@ -278,6 +287,55 @@ function checkWorkspaceBindingTarget(projectDir, workspace, registeredBlockSlugs
278
287
  }
279
288
  return createDoctorCheck(`Binding target ${bindingSource.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `${bindingSource.block}.${bindingSource.attribute} is declared and supported` : issues.join("; "));
280
289
  }
290
+ function checkWorkspaceBindingPostMeta(projectDir, inventory, bindingSource) {
291
+ if (!bindingSource.postMeta) {
292
+ return;
293
+ }
294
+ const postMeta = inventory.postMeta.find((entry) => entry.slug === bindingSource.postMeta);
295
+ if (!postMeta) {
296
+ return createDoctorCheck(`Binding post meta ${bindingSource.slug}`, "fail", `Binding source references unknown post meta contract "${bindingSource.postMeta}".`);
297
+ }
298
+ const issues = [];
299
+ try {
300
+ const fields = loadPostMetaBindingFieldsSync(projectDir, postMeta);
301
+ if (bindingSource.metaPath) {
302
+ assertPostMetaBindingPath(fields, postMeta.slug, bindingSource.metaPath);
303
+ }
304
+ } catch (error) {
305
+ issues.push(error instanceof Error ? error.message : String(error));
306
+ }
307
+ const serverPath = path3.join(projectDir, bindingSource.serverFile);
308
+ if (fs3.existsSync(serverPath)) {
309
+ const serverSource = fs3.readFileSync(serverPath, "utf8");
310
+ if (!serverSource.includes("get_post_meta")) {
311
+ issues.push(`${bindingSource.serverFile} must read post meta values`);
312
+ }
313
+ if (!serverSource.includes(postMeta.metaKey)) {
314
+ issues.push(`${bindingSource.serverFile} must reference ${postMeta.metaKey}`);
315
+ }
316
+ if (!serverSource.includes(postMeta.schemaFile)) {
317
+ issues.push(`${bindingSource.serverFile} must reference ${postMeta.schemaFile}`);
318
+ }
319
+ } else {
320
+ issues.push(`Missing ${bindingSource.serverFile}`);
321
+ }
322
+ const editorPath = path3.join(projectDir, bindingSource.editorFile);
323
+ if (fs3.existsSync(editorPath)) {
324
+ const editorSource = fs3.readFileSync(editorPath, "utf8");
325
+ if (!editorSource.includes("POST_META_BINDING_FIELDS")) {
326
+ issues.push(`${bindingSource.editorFile} must define post meta binding fields`);
327
+ }
328
+ if (!editorSource.includes(postMeta.schemaFile)) {
329
+ issues.push(`${bindingSource.editorFile} must reference ${postMeta.schemaFile}`);
330
+ }
331
+ if (bindingSource.metaPath && !editorSource.includes(bindingSource.metaPath)) {
332
+ issues.push(`${bindingSource.editorFile} must reference default meta path "${bindingSource.metaPath}"`);
333
+ }
334
+ } else {
335
+ issues.push(`Missing ${bindingSource.editorFile}`);
336
+ }
337
+ return createDoctorCheck(`Binding post meta ${bindingSource.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `${bindingSource.slug} reads ${postMeta.slug} via ${postMeta.schemaFile}` : issues.join("; "));
338
+ }
281
339
  function getWorkspaceBindingDoctorChecks(workspace, inventory) {
282
340
  const checks = [];
283
341
  if (inventory.bindingSources.length > 0) {
@@ -294,22 +352,151 @@ function getWorkspaceBindingDoctorChecks(workspace, inventory) {
294
352
  if (bindingTargetCheck) {
295
353
  checks.push(bindingTargetCheck);
296
354
  }
355
+ const bindingPostMetaCheck = checkWorkspaceBindingPostMeta(workspace.projectDir, inventory, bindingSource);
356
+ if (bindingPostMetaCheck) {
357
+ checks.push(bindingPostMetaCheck);
358
+ }
297
359
  }
298
360
  return checks;
299
361
  }
300
362
 
301
- // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-blocks.ts
363
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-block-addons.ts
302
364
  import fs4 from "fs";
303
365
  import path4 from "path";
304
- import { parseScaffoldBlockMetadata as parseScaffoldBlockMetadata2 } from "@wp-typia/block-runtime/blocks";
305
- var WORKSPACE_COLLECTION_IMPORT_LINE = "import '../../collection';";
306
- var WORKSPACE_COLLECTION_IMPORT_PATTERN = /^\s*import\s+["']\.\.\/\.\.\/collection["']\s*;?\s*$/m;
307
366
  var WORKSPACE_VARIATIONS_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceVariations\s*\}\s*from\s*["']\.\/variations["']\s*;?\s*$/mu;
308
367
  var WORKSPACE_VARIATIONS_CALL_PATTERN = /registerWorkspaceVariations\s*\(\s*\)\s*;?/u;
309
368
  var WORKSPACE_BLOCK_STYLES_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceBlockStyles\s*\}\s*from\s*["']\.\/styles["']\s*;?\s*$/mu;
310
369
  var WORKSPACE_BLOCK_STYLES_CALL_PATTERN = /registerWorkspaceBlockStyles\s*\(\s*\)\s*;?/u;
311
370
  var WORKSPACE_BLOCK_TRANSFORMS_IMPORT_PATTERN = /^\s*import\s*\{\s*applyWorkspaceBlockTransforms\s*\}\s*from\s*["']\.\/transforms["']\s*;?\s*$/mu;
312
371
  var WORKSPACE_BLOCK_TRANSFORMS_CALL_PATTERN = /applyWorkspaceBlockTransforms\s*\(\s*registration\s*\.\s*settings\s*\)\s*;?/u;
372
+ function isNestedPatternContentFile(patternFile) {
373
+ if (!patternFile) {
374
+ return false;
375
+ }
376
+ const normalizedPath = patternFile.replace(/\\/gu, "/");
377
+ return normalizedPath.startsWith("src/patterns/") && normalizedPath.slice("src/patterns/".length).includes("/");
378
+ }
379
+ function checkWorkspacePatternBootstrap(projectDir, packageName, requiresNestedPatternGlob) {
380
+ const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
381
+ if (!fs4.existsSync(bootstrapPath)) {
382
+ return createDoctorCheck("Pattern bootstrap", "fail", `Missing ${path4.basename(bootstrapPath)}`);
383
+ }
384
+ const source = fs4.readFileSync(bootstrapPath, "utf8");
385
+ const hasCategoryAnchor = source.includes("register_block_pattern_category");
386
+ const hasPatternGlob = source.includes("/src/patterns/*.php");
387
+ const hasNestedPatternGlob = source.includes("/src/patterns/*/*.php");
388
+ const hasRequiredPatternGlobs = hasPatternGlob && (!requiresNestedPatternGlob || hasNestedPatternGlob);
389
+ return createDoctorCheck("Pattern bootstrap", hasCategoryAnchor && hasRequiredPatternGlobs ? "pass" : "fail", hasCategoryAnchor && hasRequiredPatternGlobs ? "Pattern category and loader hooks are present" : requiresNestedPatternGlob ? "Missing pattern category registration or nested src/patterns loader hook" : "Missing pattern category registration or src/patterns loader hook");
390
+ }
391
+ function checkVariationEntrypoint(projectDir, blockSlug) {
392
+ const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
393
+ if (!fs4.existsSync(entryPath)) {
394
+ return createDoctorCheck(`Variation entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
395
+ }
396
+ const source = fs4.readFileSync(entryPath, "utf8");
397
+ const hasImport = hasUncommentedPattern(source, WORKSPACE_VARIATIONS_IMPORT_PATTERN);
398
+ const hasCall = hasExecutablePattern(source, WORKSPACE_VARIATIONS_CALL_PATTERN);
399
+ return createDoctorCheck(`Variation entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Variations registration hook is present" : "Missing ./variations import or registerWorkspaceVariations() call");
400
+ }
401
+ function checkBlockStyleEntrypoint(projectDir, blockSlug) {
402
+ const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
403
+ if (!fs4.existsSync(entryPath)) {
404
+ return createDoctorCheck(`Block style entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
405
+ }
406
+ const source = fs4.readFileSync(entryPath, "utf8");
407
+ const hasImport = hasUncommentedPattern(source, WORKSPACE_BLOCK_STYLES_IMPORT_PATTERN);
408
+ const hasCall = hasExecutablePattern(source, WORKSPACE_BLOCK_STYLES_CALL_PATTERN);
409
+ return createDoctorCheck(`Block style entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Block style registration hook is present" : "Missing ./styles import or registerWorkspaceBlockStyles() call");
410
+ }
411
+ function checkBlockTransformEntrypoint(projectDir, blockSlug) {
412
+ const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
413
+ if (!fs4.existsSync(entryPath)) {
414
+ return createDoctorCheck(`Block transform entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
415
+ }
416
+ const source = fs4.readFileSync(entryPath, "utf8");
417
+ const hasImport = hasUncommentedPattern(source, WORKSPACE_BLOCK_TRANSFORMS_IMPORT_PATTERN);
418
+ const hasCall = hasExecutablePattern(source, WORKSPACE_BLOCK_TRANSFORMS_CALL_PATTERN);
419
+ return createDoctorCheck(`Block transform entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Block transform registration hook is present" : "Missing ./transforms import or applyWorkspaceBlockTransforms(registration.settings) call");
420
+ }
421
+ function checkBlockTransformConfig(workspace, transform) {
422
+ const expectedTo = `${workspace.workspace.namespace}/${transform.block}`;
423
+ const issues = [];
424
+ if (!WORKSPACE_FULL_BLOCK_NAME_PATTERN.test(transform.from)) {
425
+ issues.push("from must use full namespace/block format");
426
+ }
427
+ if (transform.to !== expectedTo) {
428
+ issues.push(`to must equal "${expectedTo}" for workspace block "${transform.block}"`);
429
+ }
430
+ return createDoctorCheck(`Block transform config ${transform.block}/${transform.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `${transform.from} transforms into ${transform.to}` : issues.join("; "));
431
+ }
432
+ function getWorkspaceBlockAddonDoctorChecks(workspace, inventory, registeredBlockSlugs) {
433
+ const checks = [];
434
+ const variationTargetBlocks = new Set;
435
+ for (const variation of inventory.variations) {
436
+ if (!registeredBlockSlugs.has(variation.block)) {
437
+ checks.push(createDoctorCheck(`Variation ${variation.block}/${variation.slug}`, "fail", `Variation references unknown block "${variation.block}"`));
438
+ continue;
439
+ }
440
+ variationTargetBlocks.add(variation.block);
441
+ checks.push(checkExistingFiles(workspace.projectDir, `Variation ${variation.block}/${variation.slug}`, [variation.file]));
442
+ }
443
+ for (const blockSlug of variationTargetBlocks) {
444
+ checks.push(checkVariationEntrypoint(workspace.projectDir, blockSlug));
445
+ }
446
+ const blockStyleTargetBlocks = new Set;
447
+ for (const blockStyle of inventory.blockStyles) {
448
+ if (!registeredBlockSlugs.has(blockStyle.block)) {
449
+ checks.push(createDoctorCheck(`Block style ${blockStyle.block}/${blockStyle.slug}`, "fail", `Block style references unknown block "${blockStyle.block}"`));
450
+ continue;
451
+ }
452
+ blockStyleTargetBlocks.add(blockStyle.block);
453
+ checks.push(checkExistingFiles(workspace.projectDir, `Block style ${blockStyle.block}/${blockStyle.slug}`, [blockStyle.file]));
454
+ }
455
+ for (const blockSlug of blockStyleTargetBlocks) {
456
+ checks.push(checkExistingFiles(workspace.projectDir, `Block style registry ${blockSlug}`, [
457
+ path4.join("src", "blocks", blockSlug, "styles", "index.ts")
458
+ ]));
459
+ checks.push(checkBlockStyleEntrypoint(workspace.projectDir, blockSlug));
460
+ }
461
+ const blockTransformTargetBlocks = new Set;
462
+ for (const blockTransform of inventory.blockTransforms) {
463
+ if (!registeredBlockSlugs.has(blockTransform.block)) {
464
+ checks.push(createDoctorCheck(`Block transform ${blockTransform.block}/${blockTransform.slug}`, "fail", `Block transform references unknown block "${blockTransform.block}"`));
465
+ continue;
466
+ }
467
+ blockTransformTargetBlocks.add(blockTransform.block);
468
+ checks.push(checkBlockTransformConfig(workspace, blockTransform));
469
+ checks.push(checkExistingFiles(workspace.projectDir, `Block transform ${blockTransform.block}/${blockTransform.slug}`, [blockTransform.file]));
470
+ }
471
+ for (const blockSlug of blockTransformTargetBlocks) {
472
+ checks.push(checkExistingFiles(workspace.projectDir, `Block transform registry ${blockSlug}`, [
473
+ path4.join("src", "blocks", blockSlug, "transforms", "index.ts")
474
+ ]));
475
+ checks.push(checkBlockTransformEntrypoint(workspace.projectDir, blockSlug));
476
+ }
477
+ const shouldCheckPatternBootstrap = inventory.patterns.length > 0 || fs4.existsSync(path4.join(workspace.projectDir, "src", "patterns"));
478
+ if (shouldCheckPatternBootstrap) {
479
+ const requiresNestedPatternGlob = inventory.patterns.some((pattern) => isNestedPatternContentFile(resolvePatternCatalogContentFile(pattern)));
480
+ checks.push(checkWorkspacePatternBootstrap(workspace.projectDir, workspace.packageName, requiresNestedPatternGlob));
481
+ }
482
+ if (inventory.patterns.length > 0) {
483
+ const catalogValidation = validatePatternCatalog(inventory.patterns, {
484
+ projectDir: workspace.projectDir
485
+ });
486
+ checks.push(createDoctorCheck("Pattern catalog", catalogValidation.errors.length > 0 ? "fail" : catalogValidation.warnings.length > 0 ? "warn" : "pass", catalogValidation.diagnostics.length > 0 ? formatPatternCatalogDiagnostics(catalogValidation.diagnostics) : "Pattern catalog metadata is valid"));
487
+ }
488
+ for (const pattern of inventory.patterns) {
489
+ checks.push(checkExistingFiles(workspace.projectDir, `Pattern ${pattern.slug}`, [
490
+ resolvePatternCatalogContentFile(pattern)
491
+ ]));
492
+ }
493
+ return checks;
494
+ }
495
+
496
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-block-iframe.ts
497
+ import fs5 from "fs";
498
+ import path5 from "path";
499
+ import { parseScaffoldBlockMetadata as parseScaffoldBlockMetadata2 } from "@wp-typia/block-runtime/blocks";
313
500
  var WORKSPACE_BLOCK_IFRAME_COMPATIBILITY_DOC_URL = "https://developer.wordpress.org/block-editor/reference-guides/block-api/block-api-versions/block-migration-for-iframe-editor-compatibility/";
314
501
  var WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES = {
315
502
  API_VERSION: "wp-typia.workspace.block.iframe.api-version",
@@ -340,7 +527,7 @@ var WORKSPACE_BLOCK_LOCAL_STYLE_FILES = [
340
527
  var WORKSPACE_BLOCK_IFRAME_GLOBAL_DOM_PATTERN = /\b(?:document|window)\b|\b(?:parent|top)\b(?!\s*:)/gu;
341
528
  var WORKSPACE_BLOCK_PROPS_PATTERN = /\buse(?:Block|InnerBlocks)Props(?:\.save)?\s*\(/u;
342
529
  function normalizePathSeparators(relativePath) {
343
- return relativePath.split(path4.sep).join("/");
530
+ return relativePath.split(path5.sep).join("/");
344
531
  }
345
532
  function hasRegisteredBlockAsset(value) {
346
533
  if (typeof value === "string") {
@@ -352,16 +539,18 @@ function hasRegisteredBlockAsset(value) {
352
539
  return false;
353
540
  }
354
541
  function readWorkspaceBlockIframeMetadata(projectDir, blockSlug) {
355
- const blockJsonRelativePath = path4.join("src", "blocks", blockSlug, "block.json");
356
- const blockJsonPath = path4.join(projectDir, blockJsonRelativePath);
357
- if (!fs4.existsSync(blockJsonPath)) {
542
+ const blockJsonRelativePath = path5.join("src", "blocks", blockSlug, "block.json");
543
+ const blockJsonPath = path5.join(projectDir, blockJsonRelativePath);
544
+ if (!fs5.existsSync(blockJsonPath)) {
358
545
  return {
359
546
  blockJsonRelativePath,
360
547
  error: `Missing ${blockJsonRelativePath}`
361
548
  };
362
549
  }
363
550
  try {
364
- const document = parseScaffoldBlockMetadata2(JSON.parse(fs4.readFileSync(blockJsonPath, "utf8")));
551
+ const document = parseScaffoldBlockMetadata2(readJsonFileSync(blockJsonPath, {
552
+ context: "workspace block metadata"
553
+ }));
365
554
  return {
366
555
  blockJsonRelativePath,
367
556
  document
@@ -378,8 +567,8 @@ function isWorkspaceBlockEditorSource(relativePath) {
378
567
  return false;
379
568
  }
380
569
  const normalizedRelativePath = normalizePathSeparators(relativePath);
381
- const normalizedDirName = path4.posix.dirname(normalizedRelativePath);
382
- const normalizedBaseName = path4.posix.basename(normalizedRelativePath, path4.posix.extname(normalizedRelativePath));
570
+ const normalizedDirName = path5.posix.dirname(normalizedRelativePath);
571
+ const normalizedBaseName = path5.posix.basename(normalizedRelativePath, path5.posix.extname(normalizedRelativePath));
383
572
  if (WORKSPACE_BLOCK_EDITOR_SOURCE_BASENAMES.has(normalizedBaseName)) {
384
573
  return true;
385
574
  }
@@ -387,12 +576,12 @@ function isWorkspaceBlockEditorSource(relativePath) {
387
576
  return pathSegments.some((segment) => WORKSPACE_BLOCK_EDITOR_SOURCE_DIRECTORIES.has(segment));
388
577
  }
389
578
  function isWorkspaceBlockSaveSource(relativePath) {
390
- const normalizedBaseName = path4.basename(relativePath, path4.extname(relativePath));
579
+ const normalizedBaseName = path5.basename(relativePath, path5.extname(relativePath));
391
580
  return normalizedBaseName === "save";
392
581
  }
393
582
  function collectWorkspaceBlockEditorSources(projectDir, blockSlug) {
394
- const blockDir = path4.join(projectDir, "src", "blocks", blockSlug);
395
- if (!fs4.existsSync(blockDir)) {
583
+ const blockDir = path5.join(projectDir, "src", "blocks", blockSlug);
584
+ if (!fs5.existsSync(blockDir)) {
396
585
  return [];
397
586
  }
398
587
  const collected = [];
@@ -402,8 +591,8 @@ function collectWorkspaceBlockEditorSources(projectDir, blockSlug) {
402
591
  if (!currentDir) {
403
592
  continue;
404
593
  }
405
- for (const entry of fs4.readdirSync(currentDir, { withFileTypes: true })) {
406
- const absolutePath = path4.join(currentDir, entry.name);
594
+ for (const entry of fs5.readdirSync(currentDir, { withFileTypes: true })) {
595
+ const absolutePath = path5.join(currentDir, entry.name);
407
596
  if (entry.isDirectory()) {
408
597
  queue.push(absolutePath);
409
598
  continue;
@@ -411,13 +600,13 @@ function collectWorkspaceBlockEditorSources(projectDir, blockSlug) {
411
600
  if (!entry.isFile()) {
412
601
  continue;
413
602
  }
414
- const relativePath = path4.relative(projectDir, absolutePath);
603
+ const relativePath = path5.relative(projectDir, absolutePath);
415
604
  if (!isWorkspaceBlockEditorSource(relativePath)) {
416
605
  continue;
417
606
  }
418
607
  collected.push({
419
608
  relativePath: normalizePathSeparators(relativePath),
420
- source: fs4.readFileSync(absolutePath, "utf8")
609
+ source: fs5.readFileSync(absolutePath, "utf8")
421
610
  });
422
611
  }
423
612
  }
@@ -475,25 +664,60 @@ function findWorkspaceBlockGlobalDomAccesses(editorSources) {
475
664
  return findings;
476
665
  });
477
666
  }
667
+ function getWorkspaceBlockIframeCompatibilityChecks(projectDir, blockSlug) {
668
+ const metadataResult = readWorkspaceBlockIframeMetadata(projectDir, blockSlug);
669
+ if (!metadataResult.document) {
670
+ return [
671
+ createDoctorCheck(`Block iframe/API v3 ${blockSlug}`, "warn", metadataResult.error ?? `Unable to inspect ${metadataResult.blockJsonRelativePath}`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.API_VERSION)
672
+ ];
673
+ }
674
+ const blockJson = metadataResult.document;
675
+ const apiVersion = typeof blockJson.apiVersion === "number" && Number.isFinite(blockJson.apiVersion) ? blockJson.apiVersion : null;
676
+ const blockDir = path5.join(projectDir, "src", "blocks", blockSlug);
677
+ const localStyleFiles = WORKSPACE_BLOCK_LOCAL_STYLE_FILES.filter((fileName) => fs5.existsSync(path5.join(blockDir, fileName))).map((fileName) => normalizePathSeparators(path5.join("src", "blocks", blockSlug, fileName)));
678
+ const hasRegisteredEditorStyles = hasRegisteredBlockAsset(blockJson.style) || hasRegisteredBlockAsset(blockJson.editorStyle);
679
+ const editorSources = collectWorkspaceBlockEditorSources(projectDir, blockSlug);
680
+ const editorWrapperSources = editorSources.filter((source) => !isWorkspaceBlockSaveSource(source.relativePath));
681
+ const globalDomAccesses = findWorkspaceBlockGlobalDomAccesses(editorSources);
682
+ const hasBlockPropsUsage = editorSources.some(({ source }) => hasExecutablePattern(source, WORKSPACE_BLOCK_PROPS_PATTERN));
683
+ const hasEditorBlockPropsUsage = editorWrapperSources.some(({ source }) => hasExecutablePattern(source, WORKSPACE_BLOCK_PROPS_PATTERN));
684
+ const blockWrapperStatus = editorWrapperSources.length === 0 || hasEditorBlockPropsUsage ? "pass" : "warn";
685
+ const blockWrapperDetail = editorSources.length === 0 ? "No editor-facing block source files found; general file checks will report missing entrypoints" : editorWrapperSources.length === 0 ? "No editor wrapper source files found; general file checks will report missing entrypoints" : hasEditorBlockPropsUsage ? "Editor-facing sources use block wrapper props" : hasBlockPropsUsage ? "Only save-facing useBlockProps.save() usage was detected. Confirm the editor wrapper also receives useBlockProps() or useInnerBlocksProps() before relying on iframe editor rendering." : "No useBlockProps(), useBlockProps.save(), or useInnerBlocksProps() usage was detected in editor-facing sources. Confirm the block wrapper receives WordPress block editor props before relying on iframe editor rendering.";
686
+ return [
687
+ createDoctorCheck(`Block iframe API version ${blockSlug}`, apiVersion !== null && apiVersion >= 3 ? "pass" : "warn", apiVersion !== null && apiVersion >= 3 ? "block.json declares apiVersion 3 for iframe editor readiness" : `Set ${metadataResult.blockJsonRelativePath} apiVersion to 3 after testing the block in iframe-enabled Post Editor and Site Editor contexts. WordPress recommends API v3 for iframe editor compatibility. See ${WORKSPACE_BLOCK_IFRAME_COMPATIBILITY_DOC_URL}`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.API_VERSION),
688
+ createDoctorCheck(`Block iframe styles ${blockSlug}`, localStyleFiles.length === 0 || hasRegisteredEditorStyles ? "pass" : "warn", localStyleFiles.length === 0 ? "No local block stylesheet source files found to register" : hasRegisteredEditorStyles ? "block.json registers block styles for iframe editor loading" : `Found stylesheet source files (${localStyleFiles.join(", ")}) but block.json does not declare style or editorStyle. Register block content styles so iframe editors do not depend on parent admin styles.`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.EDITOR_STYLES),
689
+ createDoctorCheck(`Block iframe globals ${blockSlug}`, globalDomAccesses.length === 0 ? "pass" : "warn", globalDomAccesses.length === 0 ? "No direct window/document/parent DOM access detected in editor-facing block sources" : `Direct global DOM access detected at ${globalDomAccesses.join(", ")}. Prefer element.ownerDocument/defaultView via refs or useRefEffect for iframe editor content.`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.EDITOR_GLOBALS),
690
+ createDoctorCheck(`Block iframe wrapper ${blockSlug}`, blockWrapperStatus, blockWrapperDetail, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.BLOCK_PROPS)
691
+ ];
692
+ }
693
+
694
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-block-metadata.ts
695
+ import fs6 from "fs";
696
+ import path6 from "path";
697
+ import { parseScaffoldBlockMetadata as parseScaffoldBlockMetadata3 } from "@wp-typia/block-runtime/blocks";
698
+ var WORKSPACE_COLLECTION_IMPORT_LINE = "import '../../collection';";
699
+ var WORKSPACE_COLLECTION_IMPORT_PATTERN = /^\s*import\s+["']\.\.\/\.\.\/collection["']\s*;?\s*$/m;
478
700
  function getWorkspaceBlockRequiredFiles(block) {
479
- const blockDir = path4.join("src", "blocks", block.slug);
701
+ const blockDir = path6.join("src", "blocks", block.slug);
480
702
  return Array.from(new Set([
481
703
  block.typesFile,
482
704
  block.apiTypesFile,
483
705
  block.openApiFile,
484
- path4.join(blockDir, "index.tsx"),
485
- ...WORKSPACE_GENERATED_BLOCK_ARTIFACTS.map((fileName) => path4.join(blockDir, fileName))
706
+ path6.join(blockDir, "index.tsx"),
707
+ ...WORKSPACE_GENERATED_BLOCK_ARTIFACTS.map((fileName) => path6.join(blockDir, fileName))
486
708
  ].filter((filePath) => typeof filePath === "string")));
487
709
  }
488
710
  function checkWorkspaceBlockMetadata(projectDir, workspace, block) {
489
- const blockJsonRelativePath = path4.join("src", "blocks", block.slug, "block.json");
490
- const blockJsonPath = path4.join(projectDir, blockJsonRelativePath);
491
- if (!fs4.existsSync(blockJsonPath)) {
711
+ const blockJsonRelativePath = path6.join("src", "blocks", block.slug, "block.json");
712
+ const blockJsonPath = path6.join(projectDir, blockJsonRelativePath);
713
+ if (!fs6.existsSync(blockJsonPath)) {
492
714
  return createDoctorCheck(`Block metadata ${block.slug}`, "fail", `Missing ${blockJsonRelativePath}`);
493
715
  }
494
716
  let blockJson;
495
717
  try {
496
- blockJson = parseScaffoldBlockMetadata2(JSON.parse(fs4.readFileSync(blockJsonPath, "utf8")));
718
+ blockJson = parseScaffoldBlockMetadata3(readJsonFileSync(blockJsonPath, {
719
+ context: "workspace block metadata"
720
+ }));
497
721
  } catch (error) {
498
722
  return createDoctorCheck(`Block metadata ${block.slug}`, "fail", error instanceof Error ? error.message : String(error));
499
723
  }
@@ -508,14 +732,16 @@ function checkWorkspaceBlockMetadata(projectDir, workspace, block) {
508
732
  return createDoctorCheck(`Block metadata ${block.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `block.json matches ${expectedName} and ${workspace.workspace.textDomain}` : issues.join("; "));
509
733
  }
510
734
  function checkWorkspaceBlockHooks(projectDir, blockSlug) {
511
- const blockJsonRelativePath = path4.join("src", "blocks", blockSlug, "block.json");
512
- const blockJsonPath = path4.join(projectDir, blockJsonRelativePath);
513
- if (!fs4.existsSync(blockJsonPath)) {
735
+ const blockJsonRelativePath = path6.join("src", "blocks", blockSlug, "block.json");
736
+ const blockJsonPath = path6.join(projectDir, blockJsonRelativePath);
737
+ if (!fs6.existsSync(blockJsonPath)) {
514
738
  return createDoctorCheck(`Block hooks ${blockSlug}`, "fail", `Missing ${blockJsonRelativePath}`);
515
739
  }
516
740
  let blockJson;
517
741
  try {
518
- blockJson = parseScaffoldBlockMetadata2(JSON.parse(fs4.readFileSync(blockJsonPath, "utf8")));
742
+ blockJson = parseScaffoldBlockMetadata3(readJsonFileSync(blockJsonPath, {
743
+ context: "workspace block metadata"
744
+ }));
519
745
  } catch (error) {
520
746
  return createDoctorCheck(`Block hooks ${blockSlug}`, "fail", error instanceof Error ? error.message : String(error));
521
747
  }
@@ -531,311 +757,74 @@ function checkWorkspaceBlockHooks(projectDir, blockSlug) {
531
757
  return createDoctorCheck(`Block hooks ${blockSlug}`, invalidEntries.length === 0 ? "pass" : "fail", invalidEntries.length === 0 ? `blockHooks metadata is valid${Object.keys(blockHooks).length > 0 ? ` (${Object.keys(blockHooks).join(", ")})` : ""}` : `Invalid blockHooks entries: ${invalidEntries.map(([anchor, position]) => `${anchor || "<empty>"} => ${String(position)}`).join(", ")}`);
532
758
  }
533
759
  function checkWorkspaceBlockCollectionImport(projectDir, blockSlug) {
534
- const entryRelativePath = path4.join("src", "blocks", blockSlug, "index.tsx");
535
- const entryPath = path4.join(projectDir, entryRelativePath);
536
- if (!fs4.existsSync(entryPath)) {
760
+ const entryRelativePath = path6.join("src", "blocks", blockSlug, "index.tsx");
761
+ const entryPath = path6.join(projectDir, entryRelativePath);
762
+ if (!fs6.existsSync(entryPath)) {
537
763
  return createDoctorCheck(`Block collection ${blockSlug}`, "fail", `Missing ${entryRelativePath}`);
538
764
  }
539
- const source = fs4.readFileSync(entryPath, "utf8");
765
+ const source = fs6.readFileSync(entryPath, "utf8");
540
766
  const hasCollectionImport = WORKSPACE_COLLECTION_IMPORT_PATTERN.test(source);
541
767
  return createDoctorCheck(`Block collection ${blockSlug}`, hasCollectionImport ? "pass" : "fail", hasCollectionImport ? "Shared block collection import is present" : `Missing a shared collection import like ${WORKSPACE_COLLECTION_IMPORT_LINE}`);
542
768
  }
543
- function checkWorkspaceBlockIframeCompatibility(projectDir, blockSlug) {
544
- const metadataResult = readWorkspaceBlockIframeMetadata(projectDir, blockSlug);
545
- if (!metadataResult.document) {
546
- return [
547
- createDoctorCheck(`Block iframe/API v3 ${blockSlug}`, "warn", metadataResult.error ?? `Unable to inspect ${metadataResult.blockJsonRelativePath}`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.API_VERSION)
548
- ];
549
- }
550
- const blockJson = metadataResult.document;
551
- const apiVersion = typeof blockJson.apiVersion === "number" && Number.isFinite(blockJson.apiVersion) ? blockJson.apiVersion : null;
552
- const blockDir = path4.join(projectDir, "src", "blocks", blockSlug);
553
- const localStyleFiles = WORKSPACE_BLOCK_LOCAL_STYLE_FILES.filter((fileName) => fs4.existsSync(path4.join(blockDir, fileName))).map((fileName) => normalizePathSeparators(path4.join("src", "blocks", blockSlug, fileName)));
554
- const hasRegisteredEditorStyles = hasRegisteredBlockAsset(blockJson.style) || hasRegisteredBlockAsset(blockJson.editorStyle);
555
- const editorSources = collectWorkspaceBlockEditorSources(projectDir, blockSlug);
556
- const editorWrapperSources = editorSources.filter((source) => !isWorkspaceBlockSaveSource(source.relativePath));
557
- const globalDomAccesses = findWorkspaceBlockGlobalDomAccesses(editorSources);
558
- const hasBlockPropsUsage = editorSources.some(({ source }) => hasExecutablePattern(source, WORKSPACE_BLOCK_PROPS_PATTERN));
559
- const hasEditorBlockPropsUsage = editorWrapperSources.some(({ source }) => hasExecutablePattern(source, WORKSPACE_BLOCK_PROPS_PATTERN));
560
- const blockWrapperStatus = editorWrapperSources.length === 0 || hasEditorBlockPropsUsage ? "pass" : "warn";
561
- const blockWrapperDetail = editorSources.length === 0 ? "No editor-facing block source files found; general file checks will report missing entrypoints" : editorWrapperSources.length === 0 ? "No editor wrapper source files found; general file checks will report missing entrypoints" : hasEditorBlockPropsUsage ? "Editor-facing sources use block wrapper props" : hasBlockPropsUsage ? "Only save-facing useBlockProps.save() usage was detected. Confirm the editor wrapper also receives useBlockProps() or useInnerBlocksProps() before relying on iframe editor rendering." : "No useBlockProps(), useBlockProps.save(), or useInnerBlocksProps() usage was detected in editor-facing sources. Confirm the block wrapper receives WordPress block editor props before relying on iframe editor rendering.";
769
+ function getWorkspaceBlockCoreDoctorChecks(workspace, block) {
562
770
  return [
563
- createDoctorCheck(`Block iframe API version ${blockSlug}`, apiVersion !== null && apiVersion >= 3 ? "pass" : "warn", apiVersion !== null && apiVersion >= 3 ? "block.json declares apiVersion 3 for iframe editor readiness" : `Set ${metadataResult.blockJsonRelativePath} apiVersion to 3 after testing the block in iframe-enabled Post Editor and Site Editor contexts. WordPress recommends API v3 for iframe editor compatibility. See ${WORKSPACE_BLOCK_IFRAME_COMPATIBILITY_DOC_URL}`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.API_VERSION),
564
- createDoctorCheck(`Block iframe styles ${blockSlug}`, localStyleFiles.length === 0 || hasRegisteredEditorStyles ? "pass" : "warn", localStyleFiles.length === 0 ? "No local block stylesheet source files found to register" : hasRegisteredEditorStyles ? "block.json registers block styles for iframe editor loading" : `Found stylesheet source files (${localStyleFiles.join(", ")}) but block.json does not declare style or editorStyle. Register block content styles so iframe editors do not depend on parent admin styles.`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.EDITOR_STYLES),
565
- createDoctorCheck(`Block iframe globals ${blockSlug}`, globalDomAccesses.length === 0 ? "pass" : "warn", globalDomAccesses.length === 0 ? "No direct window/document/parent DOM access detected in editor-facing block sources" : `Direct global DOM access detected at ${globalDomAccesses.join(", ")}. Prefer element.ownerDocument/defaultView via refs or useRefEffect for iframe editor content.`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.EDITOR_GLOBALS),
566
- createDoctorCheck(`Block iframe wrapper ${blockSlug}`, blockWrapperStatus, blockWrapperDetail, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.BLOCK_PROPS)
771
+ checkExistingFiles(workspace.projectDir, `Block ${block.slug}`, getWorkspaceBlockRequiredFiles(block)),
772
+ checkWorkspaceBlockMetadata(workspace.projectDir, workspace, block),
773
+ checkWorkspaceBlockHooks(workspace.projectDir, block.slug),
774
+ checkWorkspaceBlockCollectionImport(workspace.projectDir, block.slug)
567
775
  ];
568
776
  }
569
- function checkWorkspacePatternBootstrap(projectDir, packageName) {
570
- const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
571
- if (!fs4.existsSync(bootstrapPath)) {
572
- return createDoctorCheck("Pattern bootstrap", "fail", `Missing ${path4.basename(bootstrapPath)}`);
777
+
778
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-blocks.ts
779
+ function getWorkspaceBlockDoctorChecks(workspace, inventory) {
780
+ const checks = [];
781
+ for (const block of inventory.blocks) {
782
+ checks.push(...getWorkspaceBlockCoreDoctorChecks(workspace, block));
783
+ checks.push(...getWorkspaceBlockIframeCompatibilityChecks(workspace.projectDir, block.slug));
573
784
  }
574
- const source = fs4.readFileSync(bootstrapPath, "utf8");
575
- const hasCategoryAnchor = source.includes("register_block_pattern_category");
576
- const hasPatternGlob = source.includes("/src/patterns/*.php");
577
- return createDoctorCheck("Pattern bootstrap", hasCategoryAnchor && hasPatternGlob ? "pass" : "fail", hasCategoryAnchor && hasPatternGlob ? "Pattern category and loader hooks are present" : "Missing pattern category registration or src/patterns loader hook");
785
+ const registeredBlockSlugs = new Set(inventory.blocks.map((block) => block.slug));
786
+ checks.push(...getWorkspaceBlockAddonDoctorChecks(workspace, inventory, registeredBlockSlugs));
787
+ return checks;
578
788
  }
579
- function checkVariationEntrypoint(projectDir, blockSlug) {
580
- const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
581
- if (!fs4.existsSync(entryPath)) {
582
- return createDoctorCheck(`Variation entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
583
- }
584
- const source = fs4.readFileSync(entryPath, "utf8");
585
- const hasImport = hasUncommentedPattern(source, WORKSPACE_VARIATIONS_IMPORT_PATTERN);
586
- const hasCall = hasExecutablePattern(source, WORKSPACE_VARIATIONS_CALL_PATTERN);
587
- return createDoctorCheck(`Variation entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Variations registration hook is present" : "Missing ./variations import or registerWorkspaceVariations() call");
789
+
790
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features-abilities.ts
791
+ import fs7 from "fs";
792
+ import path7 from "path";
793
+ function getWorkspaceAbilityRequiredFiles(ability) {
794
+ return Array.from(new Set([
795
+ ability.clientFile,
796
+ ability.configFile,
797
+ ability.dataFile,
798
+ ability.inputSchemaFile,
799
+ ability.outputSchemaFile,
800
+ ability.phpFile,
801
+ ability.typesFile
802
+ ]));
588
803
  }
589
- function checkBlockStyleEntrypoint(projectDir, blockSlug) {
590
- const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
591
- if (!fs4.existsSync(entryPath)) {
592
- return createDoctorCheck(`Block style entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
804
+ function checkWorkspaceAbilityConfig(projectDir, ability) {
805
+ const configPath = path7.join(projectDir, ability.configFile);
806
+ if (!fs7.existsSync(configPath)) {
807
+ return createDoctorCheck(`Ability config ${ability.slug}`, "fail", `Missing ${ability.configFile}`);
593
808
  }
594
- const source = fs4.readFileSync(entryPath, "utf8");
595
- const hasImport = hasUncommentedPattern(source, WORKSPACE_BLOCK_STYLES_IMPORT_PATTERN);
596
- const hasCall = hasExecutablePattern(source, WORKSPACE_BLOCK_STYLES_CALL_PATTERN);
597
- return createDoctorCheck(`Block style entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Block style registration hook is present" : "Missing ./styles import or registerWorkspaceBlockStyles() call");
598
- }
599
- function checkBlockTransformEntrypoint(projectDir, blockSlug) {
600
- const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
601
- if (!fs4.existsSync(entryPath)) {
602
- return createDoctorCheck(`Block transform entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
603
- }
604
- const source = fs4.readFileSync(entryPath, "utf8");
605
- const hasImport = hasUncommentedPattern(source, WORKSPACE_BLOCK_TRANSFORMS_IMPORT_PATTERN);
606
- const hasCall = hasExecutablePattern(source, WORKSPACE_BLOCK_TRANSFORMS_CALL_PATTERN);
607
- return createDoctorCheck(`Block transform entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Block transform registration hook is present" : "Missing ./transforms import or applyWorkspaceBlockTransforms(registration.settings) call");
608
- }
609
- function checkBlockTransformConfig(workspace, transform) {
610
- const expectedTo = `${workspace.workspace.namespace}/${transform.block}`;
611
- const issues = [];
612
- if (!WORKSPACE_FULL_BLOCK_NAME_PATTERN.test(transform.from)) {
613
- issues.push("from must use full namespace/block format");
614
- }
615
- if (transform.to !== expectedTo) {
616
- issues.push(`to must equal "${expectedTo}" for workspace block "${transform.block}"`);
617
- }
618
- return createDoctorCheck(`Block transform config ${transform.block}/${transform.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `${transform.from} transforms into ${transform.to}` : issues.join("; "));
619
- }
620
- function getWorkspaceBlockDoctorChecks(workspace, inventory) {
621
- const checks = [];
622
- for (const block of inventory.blocks) {
623
- checks.push(checkExistingFiles(workspace.projectDir, `Block ${block.slug}`, getWorkspaceBlockRequiredFiles(block)));
624
- checks.push(checkWorkspaceBlockMetadata(workspace.projectDir, workspace, block));
625
- checks.push(checkWorkspaceBlockHooks(workspace.projectDir, block.slug));
626
- checks.push(checkWorkspaceBlockCollectionImport(workspace.projectDir, block.slug));
627
- checks.push(...checkWorkspaceBlockIframeCompatibility(workspace.projectDir, block.slug));
628
- }
629
- const registeredBlockSlugs = new Set(inventory.blocks.map((block) => block.slug));
630
- const variationTargetBlocks = new Set;
631
- for (const variation of inventory.variations) {
632
- if (!registeredBlockSlugs.has(variation.block)) {
633
- checks.push(createDoctorCheck(`Variation ${variation.block}/${variation.slug}`, "fail", `Variation references unknown block "${variation.block}"`));
634
- continue;
635
- }
636
- variationTargetBlocks.add(variation.block);
637
- checks.push(checkExistingFiles(workspace.projectDir, `Variation ${variation.block}/${variation.slug}`, [variation.file]));
638
- }
639
- for (const blockSlug of variationTargetBlocks) {
640
- checks.push(checkVariationEntrypoint(workspace.projectDir, blockSlug));
641
- }
642
- const blockStyleTargetBlocks = new Set;
643
- for (const blockStyle of inventory.blockStyles) {
644
- if (!registeredBlockSlugs.has(blockStyle.block)) {
645
- checks.push(createDoctorCheck(`Block style ${blockStyle.block}/${blockStyle.slug}`, "fail", `Block style references unknown block "${blockStyle.block}"`));
646
- continue;
647
- }
648
- blockStyleTargetBlocks.add(blockStyle.block);
649
- checks.push(checkExistingFiles(workspace.projectDir, `Block style ${blockStyle.block}/${blockStyle.slug}`, [blockStyle.file]));
650
- }
651
- for (const blockSlug of blockStyleTargetBlocks) {
652
- checks.push(checkExistingFiles(workspace.projectDir, `Block style registry ${blockSlug}`, [
653
- path4.join("src", "blocks", blockSlug, "styles", "index.ts")
654
- ]));
655
- checks.push(checkBlockStyleEntrypoint(workspace.projectDir, blockSlug));
656
- }
657
- const blockTransformTargetBlocks = new Set;
658
- for (const blockTransform of inventory.blockTransforms) {
659
- if (!registeredBlockSlugs.has(blockTransform.block)) {
660
- checks.push(createDoctorCheck(`Block transform ${blockTransform.block}/${blockTransform.slug}`, "fail", `Block transform references unknown block "${blockTransform.block}"`));
661
- continue;
662
- }
663
- blockTransformTargetBlocks.add(blockTransform.block);
664
- checks.push(checkBlockTransformConfig(workspace, blockTransform));
665
- checks.push(checkExistingFiles(workspace.projectDir, `Block transform ${blockTransform.block}/${blockTransform.slug}`, [blockTransform.file]));
666
- }
667
- for (const blockSlug of blockTransformTargetBlocks) {
668
- checks.push(checkExistingFiles(workspace.projectDir, `Block transform registry ${blockSlug}`, [
669
- path4.join("src", "blocks", blockSlug, "transforms", "index.ts")
670
- ]));
671
- checks.push(checkBlockTransformEntrypoint(workspace.projectDir, blockSlug));
672
- }
673
- const shouldCheckPatternBootstrap = inventory.patterns.length > 0 || fs4.existsSync(path4.join(workspace.projectDir, "src", "patterns"));
674
- if (shouldCheckPatternBootstrap) {
675
- checks.push(checkWorkspacePatternBootstrap(workspace.projectDir, workspace.packageName));
676
- }
677
- for (const pattern of inventory.patterns) {
678
- checks.push(checkExistingFiles(workspace.projectDir, `Pattern ${pattern.slug}`, [pattern.file]));
679
- }
680
- return checks;
681
- }
682
-
683
- // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features.ts
684
- import fs5 from "fs";
685
- import path5 from "path";
686
- function isManualRestResource(restResource) {
687
- return restResource.mode === "manual";
688
- }
689
- function getWorkspaceRestResourceRequiredFiles(restResource) {
690
- const schemaNames = new Set;
691
- if (isManualRestResource(restResource)) {
692
- schemaNames.add("query");
693
- if (restResource.bodyTypeName) {
694
- schemaNames.add("request");
695
- }
696
- schemaNames.add("response");
697
- return Array.from(new Set([
698
- restResource.apiFile,
699
- ...Array.from(schemaNames, (schemaName) => path5.join(path5.dirname(restResource.typesFile), "api-schemas", `${schemaName}.schema.json`)),
700
- restResource.clientFile,
701
- restResource.openApiFile,
702
- restResource.typesFile,
703
- restResource.validatorsFile
704
- ]));
705
- }
706
- if (restResource.methods.includes("list")) {
707
- schemaNames.add("list-query");
708
- schemaNames.add("list-response");
709
- }
710
- if (restResource.methods.includes("read")) {
711
- schemaNames.add("read-query");
712
- schemaNames.add("read-response");
713
- }
714
- if (restResource.methods.includes("create")) {
715
- schemaNames.add("create-request");
716
- schemaNames.add("create-response");
717
- }
718
- if (restResource.methods.includes("update")) {
719
- schemaNames.add("update-query");
720
- schemaNames.add("update-request");
721
- schemaNames.add("update-response");
722
- }
723
- if (restResource.methods.includes("delete")) {
724
- schemaNames.add("delete-query");
725
- schemaNames.add("delete-response");
726
- }
727
- return Array.from(new Set([
728
- restResource.apiFile,
729
- ...Array.from(schemaNames, (schemaName) => path5.join(path5.dirname(restResource.typesFile), "api-schemas", `${schemaName}.schema.json`)),
730
- restResource.clientFile,
731
- ...restResource.dataFile ? [restResource.dataFile] : [],
732
- restResource.openApiFile,
733
- ...restResource.phpFile ? [restResource.phpFile] : [],
734
- restResource.typesFile,
735
- restResource.validatorsFile
736
- ]));
737
- }
738
- function checkWorkspaceRestResourceConfig(restResource) {
739
- const hasNamespace = REST_RESOURCE_NAMESPACE_PATTERN.test(restResource.namespace);
740
- if (isManualRestResource(restResource)) {
741
- const hasAuth = restResource.auth == null || MANUAL_REST_CONTRACT_AUTH_IDS.includes(restResource.auth);
742
- const hasMethod = typeof restResource.method === "string" && MANUAL_REST_CONTRACT_HTTP_METHOD_IDS.includes(restResource.method);
743
- const hasPathPattern = typeof restResource.pathPattern === "string" && restResource.pathPattern.startsWith("/") && restResource.pathPattern.length > 1;
744
- return createDoctorCheck(`REST resource config ${restResource.slug}`, hasNamespace && hasAuth && hasMethod && hasPathPattern ? "pass" : "fail", hasNamespace && hasAuth && hasMethod && hasPathPattern ? `Manual REST contract ${restResource.method} /${restResource.namespace}${restResource.pathPattern}` : "Manual REST contract namespace, auth, method, or path pattern is invalid");
745
- }
746
- const hasMethods = restResource.methods.length > 0 && restResource.methods.every((method) => REST_RESOURCE_METHOD_IDS.includes(method));
747
- const hasGeneratedFiles = typeof restResource.dataFile === "string" && restResource.dataFile.length > 0 && typeof restResource.phpFile === "string" && restResource.phpFile.length > 0;
748
- const hasRoutePattern = restResource.routePattern == null || typeof restResource.routePattern === "string" && restResource.routePattern.startsWith("/") && restResource.routePattern.length > 1 && !/\s/u.test(restResource.routePattern) && isGeneratedRestResourceRoutePatternCompatible(restResource.routePattern);
749
- return createDoctorCheck(`REST resource config ${restResource.slug}`, hasNamespace && hasMethods && hasGeneratedFiles && hasRoutePattern ? "pass" : "fail", hasNamespace && hasMethods && hasGeneratedFiles && hasRoutePattern ? `REST resource namespace ${restResource.namespace} with methods ${restResource.methods.join(", ")}` : "REST resource namespace, methods, dataFile, phpFile, or routePattern are invalid");
750
- }
751
- function checkWorkspaceRestResourceBootstrap(projectDir, packageName, phpPrefix) {
752
- const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
753
- if (!fs5.existsSync(bootstrapPath)) {
754
- return createDoctorCheck("REST resource bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
755
- }
756
- const source = fs5.readFileSync(bootstrapPath, "utf8");
757
- const registerFunctionName = `${phpPrefix}_register_rest_resources`;
758
- const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
759
- const hasServerGlob = source.includes(WORKSPACE_REST_RESOURCE_GLOB);
760
- const hasRegisterHook = source.includes(registerHook);
761
- return createDoctorCheck("REST resource bootstrap", hasServerGlob && hasRegisterHook ? "pass" : "fail", hasServerGlob && hasRegisterHook ? "REST resource PHP loader hook is present" : "Missing REST resource PHP require glob or init hook");
762
- }
763
- function getWorkspacePostMetaRequiredFiles(postMeta) {
764
- return Array.from(new Set([
765
- postMeta.phpFile,
766
- postMeta.schemaFile,
767
- postMeta.typesFile
768
- ]));
769
- }
770
- function checkWorkspacePostMetaConfig(postMeta) {
771
- let hasPostType = false;
772
- try {
773
- hasPostType = assertValidPostMetaPostType(postMeta.postType) === postMeta.postType;
774
- } catch {
775
- hasPostType = false;
776
- }
777
- const hasMetaKey = typeof postMeta.metaKey === "string" && postMeta.metaKey.trim().length > 0 && !/\s/u.test(postMeta.metaKey);
778
- const hasRestExposure = typeof postMeta.showInRest === "boolean";
779
- return createDoctorCheck(`Post meta config ${postMeta.slug}`, hasPostType && hasMetaKey && hasRestExposure ? "pass" : "fail", hasPostType && hasMetaKey && hasRestExposure ? `Post meta ${postMeta.metaKey} targets ${postMeta.postType}` : "Post meta postType, metaKey, or showInRest configuration is invalid");
780
- }
781
- function checkWorkspacePostMetaBootstrap(projectDir, packageName, phpPrefix) {
782
- const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
783
- if (!fs5.existsSync(bootstrapPath)) {
784
- return createDoctorCheck("Post meta bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
785
- }
786
- const source = fs5.readFileSync(bootstrapPath, "utf8");
787
- const registerFunctionName = `${phpPrefix}_register_post_meta_contracts`;
788
- const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
789
- const hasServerGlob = source.includes(WORKSPACE_POST_META_GLOB);
790
- const hasRegisterHook = source.includes(registerHook);
791
- return createDoctorCheck("Post meta bootstrap", hasServerGlob && hasRegisterHook ? "pass" : "fail", hasServerGlob && hasRegisterHook ? "Post meta PHP loader hook is present" : "Missing post meta PHP require glob or init hook");
792
- }
793
- function checkWorkspacePostMetaPhp(projectDir, postMeta) {
794
- const phpPath = path5.join(projectDir, postMeta.phpFile);
795
- if (!fs5.existsSync(phpPath)) {
796
- return createDoctorCheck(`Post meta PHP ${postMeta.slug}`, "fail", `Missing ${postMeta.phpFile}`);
797
- }
798
- const source = fs5.readFileSync(phpPath, "utf8");
799
- const hasRegisterPostMeta = source.includes("register_post_meta");
800
- const hasPostType = source.includes(postMeta.postType);
801
- const hasMetaKey = source.includes(postMeta.metaKey);
802
- const hasSchemaFile = source.includes(postMeta.schemaFile);
803
- const hasRestExposure = source.includes("'show_in_rest'");
804
- return createDoctorCheck(`Post meta PHP ${postMeta.slug}`, hasRegisterPostMeta && hasPostType && hasMetaKey && hasSchemaFile && hasRestExposure ? "pass" : "fail", hasRegisterPostMeta && hasPostType && hasMetaKey && hasSchemaFile && hasRestExposure ? "Post meta registration, schema path, and REST exposure flag are wired" : "Missing register_post_meta, post type, meta key, schema path, or show_in_rest wiring");
805
- }
806
- function getWorkspaceAbilityRequiredFiles(ability) {
807
- return Array.from(new Set([
808
- ability.clientFile,
809
- ability.configFile,
810
- ability.dataFile,
811
- ability.inputSchemaFile,
812
- ability.outputSchemaFile,
813
- ability.phpFile,
814
- ability.typesFile
815
- ]));
816
- }
817
- function checkWorkspaceAbilityConfig(projectDir, ability) {
818
- const configPath = path5.join(projectDir, ability.configFile);
819
- if (!fs5.existsSync(configPath)) {
820
- return createDoctorCheck(`Ability config ${ability.slug}`, "fail", `Missing ${ability.configFile}`);
821
- }
822
- try {
823
- const config = JSON.parse(fs5.readFileSync(configPath, "utf8"));
824
- const abilityId = typeof config.abilityId === "string" ? config.abilityId.trim() : "";
825
- const categorySlug = typeof config.category?.slug === "string" ? config.category.slug.trim() : "";
826
- const hasValidAbilityId = /^[a-z0-9-]+\/[a-z0-9-]+$/u.test(abilityId);
827
- const hasValidCategorySlug = /^[a-z0-9-]+$/u.test(categorySlug);
828
- return createDoctorCheck(`Ability config ${ability.slug}`, hasValidAbilityId && hasValidCategorySlug ? "pass" : "fail", hasValidAbilityId && hasValidCategorySlug ? `Ability id ${abilityId} in category ${categorySlug} is valid` : "Ability config must define a valid abilityId (`namespace/ability-name`) and category.slug.");
829
- } catch (error) {
830
- return createDoctorCheck(`Ability config ${ability.slug}`, "fail", error instanceof Error ? error.message : String(error));
809
+ try {
810
+ const config = readJsonFileSync(configPath, {
811
+ context: "workspace ability config"
812
+ });
813
+ const abilityId = typeof config.abilityId === "string" ? config.abilityId.trim() : "";
814
+ const categorySlug = typeof config.category?.slug === "string" ? config.category.slug.trim() : "";
815
+ const hasValidAbilityId = /^[a-z0-9-]+\/[a-z0-9-]+$/u.test(abilityId);
816
+ const hasValidCategorySlug = /^[a-z0-9-]+$/u.test(categorySlug);
817
+ return createDoctorCheck(`Ability config ${ability.slug}`, hasValidAbilityId && hasValidCategorySlug ? "pass" : "fail", hasValidAbilityId && hasValidCategorySlug ? `Ability id ${abilityId} in category ${categorySlug} is valid` : "Ability config must define a valid abilityId (`namespace/ability-name`) and category.slug.");
818
+ } catch (error) {
819
+ return createDoctorCheck(`Ability config ${ability.slug}`, "fail", error instanceof Error ? error.message : String(error));
831
820
  }
832
821
  }
833
822
  function checkWorkspaceAbilityBootstrap(projectDir, packageName, phpPrefix) {
834
823
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
835
- if (!fs5.existsSync(bootstrapPath)) {
836
- return createDoctorCheck("Ability bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
824
+ if (!fs7.existsSync(bootstrapPath)) {
825
+ return createDoctorCheck("Ability bootstrap", "fail", `Missing ${path7.basename(bootstrapPath)}`);
837
826
  }
838
- const source = fs5.readFileSync(bootstrapPath, "utf8");
827
+ const source = fs7.readFileSync(bootstrapPath, "utf8");
839
828
  const loadFunctionName = `${phpPrefix}_load_workflow_abilities`;
840
829
  const enqueueFunctionName = `${phpPrefix}_enqueue_workflow_abilities`;
841
830
  const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
@@ -852,27 +841,130 @@ function checkWorkspaceAbilityBootstrap(projectDir, packageName, phpPrefix) {
852
841
  }
853
842
  function checkWorkspaceAbilityIndex(projectDir, abilities) {
854
843
  const indexRelativePath = [
855
- path5.join("src", "abilities", "index.ts"),
856
- path5.join("src", "abilities", "index.js")
857
- ].find((relativePath) => fs5.existsSync(path5.join(projectDir, relativePath)));
844
+ path7.join("src", "abilities", "index.ts"),
845
+ path7.join("src", "abilities", "index.js")
846
+ ].find((relativePath) => fs7.existsSync(path7.join(projectDir, relativePath)));
858
847
  if (!indexRelativePath) {
859
848
  return createDoctorCheck("Abilities index", "fail", "Missing src/abilities/index.ts or src/abilities/index.js");
860
849
  }
861
- const indexPath = path5.join(projectDir, indexRelativePath);
862
- const source = fs5.readFileSync(indexPath, "utf8");
850
+ const indexPath = path7.join(projectDir, indexRelativePath);
851
+ const source = fs7.readFileSync(indexPath, "utf8");
863
852
  const missingExports = abilities.filter((ability) => {
864
853
  const exportPattern = new RegExp(`^\\s*export\\s+(?:\\*\\s+from|\\{[^}]+\\}\\s+from)\\s+['"\`]\\./${escapeRegex(ability.slug)}\\/client['"\`]`, "mu");
865
854
  return !exportPattern.test(source);
866
855
  });
867
856
  return createDoctorCheck("Abilities index", missingExports.length === 0 ? "pass" : "fail", missingExports.length === 0 ? "Ability client helpers are aggregated" : `Missing ability exports for: ${missingExports.map((entry) => entry.slug).join(", ")}`);
868
857
  }
858
+ function getWorkspaceAbilityDoctorChecks(workspace, abilities) {
859
+ const checks = [];
860
+ if (abilities.length > 0) {
861
+ checks.push(checkWorkspaceAbilityBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
862
+ checks.push(checkWorkspaceAbilityIndex(workspace.projectDir, abilities));
863
+ }
864
+ for (const ability of abilities) {
865
+ checks.push(checkWorkspaceAbilityConfig(workspace.projectDir, ability));
866
+ checks.push(checkExistingFiles(workspace.projectDir, `Ability ${ability.slug}`, getWorkspaceAbilityRequiredFiles(ability)));
867
+ }
868
+ return checks;
869
+ }
870
+
871
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features-admin-views.ts
872
+ import fs8 from "fs";
873
+ import path8 from "path";
874
+ function getWorkspaceAdminViewRequiredFiles(adminView) {
875
+ const adminViewDir = path8.join("src", "admin-views", adminView.slug);
876
+ return Array.from(new Set([
877
+ adminView.file,
878
+ adminView.phpFile,
879
+ path8.join(adminViewDir, "Screen.tsx"),
880
+ path8.join(adminViewDir, "config.ts"),
881
+ path8.join(adminViewDir, "data.ts"),
882
+ path8.join(adminViewDir, "style.scss"),
883
+ path8.join(adminViewDir, "types.ts")
884
+ ]));
885
+ }
886
+ function checkWorkspaceAdminViewConfig(adminView, inventory) {
887
+ if (adminView.source === undefined) {
888
+ return createDoctorCheck(`Admin view config ${adminView.slug}`, "pass", "Admin view uses a replaceable local fetcher");
889
+ }
890
+ const source = adminView.source.trim();
891
+ const restSourceMatch = /^rest-resource:([a-z][a-z0-9-]*)$/u.exec(source);
892
+ const coreDataSourceMatch = /^core-data:(postType|taxonomy)\/([a-z0-9][a-z0-9_-]*)$/u.exec(source);
893
+ const restResourceSlug = restSourceMatch?.[1];
894
+ const restResource = restResourceSlug ? inventory.restResources.find((entry) => entry.slug === restResourceSlug) : undefined;
895
+ const isListCapableRestResource = Boolean(restResource?.methods.includes("list"));
896
+ const isManualSettingsRestResource = isAdminViewManualSettingsRestResource(restResource);
897
+ const hasManualSettingsRouteParameters = isManualSettingsRestResource && hasAdminViewManualSettingsRouteParameters(restResource);
898
+ const isValid = isListCapableRestResource || isManualSettingsRestResource && !hasManualSettingsRouteParameters || Boolean(coreDataSourceMatch);
899
+ const failDetail = hasManualSettingsRouteParameters ? `Admin view source ${source} uses route parameters or regex groups and cannot scaffold a singleton settings form` : "Admin view source must use rest-resource:<slug> with a list-capable REST resource, a manual settings contract with a body type, or core-data:<postType|taxonomy>/<name>";
900
+ return createDoctorCheck(`Admin view config ${adminView.slug}`, isValid ? "pass" : "fail", isValid ? `Admin view source ${source} is ${isManualSettingsRestResource ? "settings-form capable" : coreDataSourceMatch ? "core-data capable" : "list-capable"}` : failDetail);
901
+ }
902
+ function checkWorkspaceAdminViewBootstrap(projectDir, packageName, phpPrefix) {
903
+ const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
904
+ if (!fs8.existsSync(bootstrapPath)) {
905
+ return createDoctorCheck("Admin view bootstrap", "fail", `Missing ${path8.basename(bootstrapPath)}`);
906
+ }
907
+ const source = fs8.readFileSync(bootstrapPath, "utf8");
908
+ const loadFunctionName = `${phpPrefix}_load_admin_views`;
909
+ const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
910
+ const hasLoaderHook = source.includes(loadHook);
911
+ const hasServerGlob = source.includes(WORKSPACE_ADMIN_VIEW_GLOB);
912
+ return createDoctorCheck("Admin view bootstrap", hasLoaderHook && hasServerGlob ? "pass" : "fail", hasLoaderHook && hasServerGlob ? "Admin view PHP loader hook is present" : "Missing admin view PHP require glob or plugins_loaded hook");
913
+ }
914
+ function checkWorkspaceAdminViewIndex(projectDir, adminViews) {
915
+ const indexRelativePath = [
916
+ path8.join("src", "admin-views", "index.ts"),
917
+ path8.join("src", "admin-views", "index.js")
918
+ ].find((relativePath) => fs8.existsSync(path8.join(projectDir, relativePath)));
919
+ if (!indexRelativePath) {
920
+ return createDoctorCheck("Admin views index", "fail", "Missing src/admin-views/index.ts or src/admin-views/index.js");
921
+ }
922
+ const indexPath = path8.join(projectDir, indexRelativePath);
923
+ const source = fs8.readFileSync(indexPath, "utf8");
924
+ const missingImports = adminViews.filter((adminView) => {
925
+ const importPattern = new RegExp(`['"\`]\\./${escapeRegex(adminView.slug)}(?:/[^'"\`]*)?['"\`]`, "u");
926
+ return !importPattern.test(source);
927
+ });
928
+ return createDoctorCheck("Admin views index", missingImports.length === 0 ? "pass" : "fail", missingImports.length === 0 ? "Admin view registrations are aggregated" : `Missing admin view imports for: ${missingImports.map((entry) => entry.slug).join(", ")}`);
929
+ }
930
+ function checkWorkspaceAdminViewPhp(projectDir, adminView) {
931
+ const phpPath = path8.join(projectDir, adminView.phpFile);
932
+ if (!fs8.existsSync(phpPath)) {
933
+ return createDoctorCheck(`Admin view PHP ${adminView.slug}`, "fail", `Missing ${adminView.phpFile}`);
934
+ }
935
+ const source = fs8.readFileSync(phpPath, "utf8");
936
+ const hasAdminMenu = source.includes("add_submenu_page");
937
+ const hasAdminEnqueue = source.includes("admin_enqueue_scripts");
938
+ const hasScript = source.includes(WORKSPACE_ADMIN_VIEW_SCRIPT);
939
+ const hasAsset = source.includes(WORKSPACE_ADMIN_VIEW_ASSET);
940
+ const hasStyle = source.includes(WORKSPACE_ADMIN_VIEW_STYLE);
941
+ const hasComponentsStyleDependency = /['"]wp-components['"]/u.test(source);
942
+ return createDoctorCheck(`Admin view PHP ${adminView.slug}`, hasAdminMenu && hasAdminEnqueue && hasScript && hasAsset && hasStyle && hasComponentsStyleDependency ? "pass" : "fail", hasAdminMenu && hasAdminEnqueue && hasScript && hasAsset && hasStyle && hasComponentsStyleDependency ? "Admin menu, script, style, and wp-components style dependency are wired" : "Missing admin menu, enqueue hook, build/admin-views asset reference, or wp-components style dependency");
943
+ }
944
+ function getWorkspaceAdminViewDoctorChecks(workspace, inventory) {
945
+ const checks = [];
946
+ if (inventory.adminViews.length > 0) {
947
+ checks.push(checkWorkspaceAdminViewBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
948
+ checks.push(checkWorkspaceAdminViewIndex(workspace.projectDir, inventory.adminViews));
949
+ }
950
+ for (const adminView of inventory.adminViews) {
951
+ checks.push(checkWorkspaceAdminViewConfig(adminView, inventory));
952
+ checks.push(checkExistingFiles(workspace.projectDir, `Admin view ${adminView.slug}`, getWorkspaceAdminViewRequiredFiles(adminView)));
953
+ checks.push(checkWorkspaceAdminViewPhp(workspace.projectDir, adminView));
954
+ }
955
+ return checks;
956
+ }
957
+
958
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features-ai.ts
959
+ import fs9 from "fs";
960
+ import path9 from "path";
869
961
  function getWorkspaceAiFeatureRequiredFiles(aiFeature) {
870
962
  return Array.from(new Set([
871
963
  aiFeature.aiSchemaFile,
872
964
  aiFeature.apiFile,
873
- path5.join(path5.dirname(aiFeature.typesFile), "api-schemas", "feature-request.schema.json"),
874
- path5.join(path5.dirname(aiFeature.typesFile), "api-schemas", "feature-response.schema.json"),
875
- path5.join(path5.dirname(aiFeature.typesFile), "api-schemas", "feature-result.schema.json"),
965
+ path9.join(path9.dirname(aiFeature.typesFile), "api-schemas", "feature-request.schema.json"),
966
+ path9.join(path9.dirname(aiFeature.typesFile), "api-schemas", "feature-response.schema.json"),
967
+ path9.join(path9.dirname(aiFeature.typesFile), "api-schemas", "feature-result.schema.json"),
876
968
  aiFeature.clientFile,
877
969
  aiFeature.dataFile,
878
970
  aiFeature.openApiFile,
@@ -887,25 +979,40 @@ function checkWorkspaceAiFeatureConfig(aiFeature) {
887
979
  }
888
980
  function checkWorkspaceAiFeatureBootstrap(projectDir, packageName, phpPrefix) {
889
981
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
890
- if (!fs5.existsSync(bootstrapPath)) {
891
- return createDoctorCheck("AI feature bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
982
+ if (!fs9.existsSync(bootstrapPath)) {
983
+ return createDoctorCheck("AI feature bootstrap", "fail", `Missing ${path9.basename(bootstrapPath)}`);
892
984
  }
893
- const source = fs5.readFileSync(bootstrapPath, "utf8");
985
+ const source = fs9.readFileSync(bootstrapPath, "utf8");
894
986
  const registerFunctionName = `${phpPrefix}_register_ai_features`;
895
987
  const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
896
988
  const hasServerGlob = source.includes(WORKSPACE_AI_FEATURE_GLOB);
897
989
  const hasRegisterHook = source.includes(registerHook);
898
990
  return createDoctorCheck("AI feature bootstrap", hasServerGlob && hasRegisterHook ? "pass" : "fail", hasServerGlob && hasRegisterHook ? "AI feature PHP loader hook is present" : "Missing AI feature PHP require glob or init hook");
899
991
  }
992
+ function getWorkspaceAiFeatureDoctorChecks(workspace, aiFeatures) {
993
+ const checks = [];
994
+ if (aiFeatures.length > 0) {
995
+ checks.push(checkWorkspaceAiFeatureBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
996
+ }
997
+ for (const aiFeature of aiFeatures) {
998
+ checks.push(checkWorkspaceAiFeatureConfig(aiFeature));
999
+ checks.push(checkExistingFiles(workspace.projectDir, `AI feature ${aiFeature.slug}`, getWorkspaceAiFeatureRequiredFiles(aiFeature)));
1000
+ }
1001
+ return checks;
1002
+ }
1003
+
1004
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features-editor-plugins.ts
1005
+ import fs10 from "fs";
1006
+ import path10 from "path";
900
1007
  function getWorkspaceEditorPluginRequiredFiles(editorPlugin) {
901
- const editorPluginDir = path5.join("src", "editor-plugins", editorPlugin.slug);
902
- const surfaceFile = editorPlugin.slot === "PluginSidebar" ? path5.join(editorPluginDir, "Sidebar.tsx") : path5.join(editorPluginDir, "Surface.tsx");
1008
+ const editorPluginDir = path10.join("src", "editor-plugins", editorPlugin.slug);
1009
+ const surfaceFile = editorPlugin.slot === "PluginSidebar" ? path10.join(editorPluginDir, "Sidebar.tsx") : path10.join(editorPluginDir, "Surface.tsx");
903
1010
  return Array.from(new Set([
904
1011
  editorPlugin.file,
905
1012
  surfaceFile,
906
- path5.join(editorPluginDir, "data.ts"),
907
- path5.join(editorPluginDir, "types.ts"),
908
- path5.join(editorPluginDir, "style.scss")
1013
+ path10.join(editorPluginDir, "data.ts"),
1014
+ path10.join(editorPluginDir, "types.ts"),
1015
+ path10.join(editorPluginDir, "style.scss")
909
1016
  ]));
910
1017
  }
911
1018
  function checkWorkspaceEditorPluginConfig(editorPlugin) {
@@ -915,10 +1022,10 @@ function checkWorkspaceEditorPluginConfig(editorPlugin) {
915
1022
  }
916
1023
  function checkWorkspaceEditorPluginBootstrap(projectDir, packageName, phpPrefix) {
917
1024
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
918
- if (!fs5.existsSync(bootstrapPath)) {
919
- return createDoctorCheck("Editor plugin bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
1025
+ if (!fs10.existsSync(bootstrapPath)) {
1026
+ return createDoctorCheck("Editor plugin bootstrap", "fail", `Missing ${path10.basename(bootstrapPath)}`);
920
1027
  }
921
- const source = fs5.readFileSync(bootstrapPath, "utf8");
1028
+ const source = fs10.readFileSync(bootstrapPath, "utf8");
922
1029
  const enqueueFunctionName = `${phpPrefix}_enqueue_editor_plugins_editor`;
923
1030
  const enqueueHook = `add_action( 'enqueue_block_editor_assets', '${enqueueFunctionName}' );`;
924
1031
  const hasEditorEnqueueHook = source.includes(enqueueHook);
@@ -929,151 +1036,205 @@ function checkWorkspaceEditorPluginBootstrap(projectDir, packageName, phpPrefix)
929
1036
  }
930
1037
  function checkWorkspaceEditorPluginIndex(projectDir, editorPlugins) {
931
1038
  const indexRelativePath = [
932
- path5.join("src", "editor-plugins", "index.ts"),
933
- path5.join("src", "editor-plugins", "index.js")
934
- ].find((relativePath) => fs5.existsSync(path5.join(projectDir, relativePath)));
1039
+ path10.join("src", "editor-plugins", "index.ts"),
1040
+ path10.join("src", "editor-plugins", "index.js")
1041
+ ].find((relativePath) => fs10.existsSync(path10.join(projectDir, relativePath)));
935
1042
  if (!indexRelativePath) {
936
1043
  return createDoctorCheck("Editor plugins index", "fail", "Missing src/editor-plugins/index.ts or src/editor-plugins/index.js");
937
1044
  }
938
- const indexPath = path5.join(projectDir, indexRelativePath);
939
- const source = fs5.readFileSync(indexPath, "utf8");
1045
+ const indexPath = path10.join(projectDir, indexRelativePath);
1046
+ const source = fs10.readFileSync(indexPath, "utf8");
940
1047
  const missingImports = editorPlugins.filter((editorPlugin) => {
941
1048
  const importPattern = new RegExp(`['"\`]\\./${escapeRegex(editorPlugin.slug)}(?:/[^'"\`]*)?['"\`]`, "u");
942
1049
  return !importPattern.test(source);
943
1050
  });
944
1051
  return createDoctorCheck("Editor plugins index", missingImports.length === 0 ? "pass" : "fail", missingImports.length === 0 ? "Editor plugin registrations are aggregated" : `Missing editor plugin imports for: ${missingImports.map((entry) => entry.slug).join(", ")}`);
945
1052
  }
946
- function getWorkspaceAdminViewRequiredFiles(adminView) {
947
- const adminViewDir = path5.join("src", "admin-views", adminView.slug);
1053
+ function getWorkspaceEditorPluginDoctorChecks(workspace, editorPlugins) {
1054
+ const checks = [];
1055
+ if (editorPlugins.length > 0) {
1056
+ checks.push(checkWorkspaceEditorPluginBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1057
+ checks.push(checkWorkspaceEditorPluginIndex(workspace.projectDir, editorPlugins));
1058
+ }
1059
+ for (const editorPlugin of editorPlugins) {
1060
+ checks.push(checkExistingFiles(workspace.projectDir, `Editor plugin ${editorPlugin.slug}`, getWorkspaceEditorPluginRequiredFiles(editorPlugin)));
1061
+ checks.push(checkWorkspaceEditorPluginConfig(editorPlugin));
1062
+ }
1063
+ return checks;
1064
+ }
1065
+
1066
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features-post-meta.ts
1067
+ import fs11 from "fs";
1068
+ import path11 from "path";
1069
+ function getWorkspacePostMetaRequiredFiles(postMeta) {
948
1070
  return Array.from(new Set([
949
- adminView.file,
950
- adminView.phpFile,
951
- path5.join(adminViewDir, "Screen.tsx"),
952
- path5.join(adminViewDir, "config.ts"),
953
- path5.join(adminViewDir, "data.ts"),
954
- path5.join(adminViewDir, "style.scss"),
955
- path5.join(adminViewDir, "types.ts")
1071
+ postMeta.phpFile,
1072
+ postMeta.schemaFile,
1073
+ postMeta.typesFile
956
1074
  ]));
957
1075
  }
958
- function checkWorkspaceAdminViewConfig(adminView, inventory) {
959
- if (adminView.source === undefined) {
960
- return createDoctorCheck(`Admin view config ${adminView.slug}`, "pass", "Admin view uses a replaceable local fetcher");
1076
+ function checkWorkspacePostMetaConfig(postMeta) {
1077
+ let hasPostType = false;
1078
+ try {
1079
+ hasPostType = assertValidPostMetaPostType(postMeta.postType) === postMeta.postType;
1080
+ } catch {
1081
+ hasPostType = false;
961
1082
  }
962
- const source = adminView.source.trim();
963
- const restSourceMatch = /^rest-resource:([a-z][a-z0-9-]*)$/u.exec(source);
964
- const coreDataSourceMatch = /^core-data:(postType|taxonomy)\/([a-z0-9][a-z0-9_-]*)$/u.exec(source);
965
- const restResourceSlug = restSourceMatch?.[1];
966
- const restResource = restResourceSlug ? inventory.restResources.find((entry) => entry.slug === restResourceSlug) : undefined;
967
- const isListCapableRestResource = Boolean(restResource?.methods.includes("list"));
968
- const isManualSettingsRestResource = isAdminViewManualSettingsRestResource(restResource);
969
- const hasManualSettingsRouteParameters = isManualSettingsRestResource && hasAdminViewManualSettingsRouteParameters(restResource);
970
- const isValid = isListCapableRestResource || isManualSettingsRestResource && !hasManualSettingsRouteParameters || Boolean(coreDataSourceMatch);
971
- const failDetail = hasManualSettingsRouteParameters ? `Admin view source ${source} uses route parameters or regex groups and cannot scaffold a singleton settings form` : "Admin view source must use rest-resource:<slug> with a list-capable REST resource, a manual settings contract with a body type, or core-data:<postType|taxonomy>/<name>";
972
- return createDoctorCheck(`Admin view config ${adminView.slug}`, isValid ? "pass" : "fail", isValid ? `Admin view source ${source} is ${isManualSettingsRestResource ? "settings-form capable" : coreDataSourceMatch ? "core-data capable" : "list-capable"}` : failDetail);
1083
+ const hasMetaKey = typeof postMeta.metaKey === "string" && postMeta.metaKey.trim().length > 0 && !/\s/u.test(postMeta.metaKey);
1084
+ const hasRestExposure = typeof postMeta.showInRest === "boolean";
1085
+ return createDoctorCheck(`Post meta config ${postMeta.slug}`, hasPostType && hasMetaKey && hasRestExposure ? "pass" : "fail", hasPostType && hasMetaKey && hasRestExposure ? `Post meta ${postMeta.metaKey} targets ${postMeta.postType}` : "Post meta postType, metaKey, or showInRest configuration is invalid");
973
1086
  }
974
- function checkWorkspaceAdminViewBootstrap(projectDir, packageName, phpPrefix) {
1087
+ function checkWorkspacePostMetaBootstrap(projectDir, packageName, phpPrefix) {
975
1088
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
976
- if (!fs5.existsSync(bootstrapPath)) {
977
- return createDoctorCheck("Admin view bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
978
- }
979
- const source = fs5.readFileSync(bootstrapPath, "utf8");
980
- const loadFunctionName = `${phpPrefix}_load_admin_views`;
981
- const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
982
- const hasLoaderHook = source.includes(loadHook);
983
- const hasServerGlob = source.includes(WORKSPACE_ADMIN_VIEW_GLOB);
984
- return createDoctorCheck("Admin view bootstrap", hasLoaderHook && hasServerGlob ? "pass" : "fail", hasLoaderHook && hasServerGlob ? "Admin view PHP loader hook is present" : "Missing admin view PHP require glob or plugins_loaded hook");
985
- }
986
- function checkWorkspaceAdminViewIndex(projectDir, adminViews) {
987
- const indexRelativePath = [
988
- path5.join("src", "admin-views", "index.ts"),
989
- path5.join("src", "admin-views", "index.js")
990
- ].find((relativePath) => fs5.existsSync(path5.join(projectDir, relativePath)));
991
- if (!indexRelativePath) {
992
- return createDoctorCheck("Admin views index", "fail", "Missing src/admin-views/index.ts or src/admin-views/index.js");
1089
+ if (!fs11.existsSync(bootstrapPath)) {
1090
+ return createDoctorCheck("Post meta bootstrap", "fail", `Missing ${path11.basename(bootstrapPath)}`);
993
1091
  }
994
- const indexPath = path5.join(projectDir, indexRelativePath);
995
- const source = fs5.readFileSync(indexPath, "utf8");
996
- const missingImports = adminViews.filter((adminView) => {
997
- const importPattern = new RegExp(`['"\`]\\./${escapeRegex(adminView.slug)}(?:/[^'"\`]*)?['"\`]`, "u");
998
- return !importPattern.test(source);
999
- });
1000
- return createDoctorCheck("Admin views index", missingImports.length === 0 ? "pass" : "fail", missingImports.length === 0 ? "Admin view registrations are aggregated" : `Missing admin view imports for: ${missingImports.map((entry) => entry.slug).join(", ")}`);
1092
+ const source = fs11.readFileSync(bootstrapPath, "utf8");
1093
+ const registerFunctionName = `${phpPrefix}_register_post_meta_contracts`;
1094
+ const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
1095
+ const hasServerGlob = source.includes(WORKSPACE_POST_META_GLOB);
1096
+ const hasRegisterHook = source.includes(registerHook);
1097
+ return createDoctorCheck("Post meta bootstrap", hasServerGlob && hasRegisterHook ? "pass" : "fail", hasServerGlob && hasRegisterHook ? "Post meta PHP loader hook is present" : "Missing post meta PHP require glob or init hook");
1001
1098
  }
1002
- function checkWorkspaceAdminViewPhp(projectDir, adminView) {
1003
- const phpPath = path5.join(projectDir, adminView.phpFile);
1004
- if (!fs5.existsSync(phpPath)) {
1005
- return createDoctorCheck(`Admin view PHP ${adminView.slug}`, "fail", `Missing ${adminView.phpFile}`);
1099
+ function checkWorkspacePostMetaPhp(projectDir, postMeta) {
1100
+ const phpPath = path11.join(projectDir, postMeta.phpFile);
1101
+ if (!fs11.existsSync(phpPath)) {
1102
+ return createDoctorCheck(`Post meta PHP ${postMeta.slug}`, "fail", `Missing ${postMeta.phpFile}`);
1006
1103
  }
1007
- const source = fs5.readFileSync(phpPath, "utf8");
1008
- const hasAdminMenu = source.includes("add_submenu_page");
1009
- const hasAdminEnqueue = source.includes("admin_enqueue_scripts");
1010
- const hasScript = source.includes(WORKSPACE_ADMIN_VIEW_SCRIPT);
1011
- const hasAsset = source.includes(WORKSPACE_ADMIN_VIEW_ASSET);
1012
- const hasStyle = source.includes(WORKSPACE_ADMIN_VIEW_STYLE);
1013
- const hasComponentsStyleDependency = source.includes("'wp-components'");
1014
- return createDoctorCheck(`Admin view PHP ${adminView.slug}`, hasAdminMenu && hasAdminEnqueue && hasScript && hasAsset && hasStyle && hasComponentsStyleDependency ? "pass" : "fail", hasAdminMenu && hasAdminEnqueue && hasScript && hasAsset && hasStyle && hasComponentsStyleDependency ? "Admin menu, script, style, and wp-components style dependency are wired" : "Missing admin menu, enqueue hook, build/admin-views asset reference, or wp-components style dependency");
1104
+ const source = fs11.readFileSync(phpPath, "utf8");
1105
+ const hasRegisterPostMeta = source.includes("register_post_meta");
1106
+ const hasPostType = source.includes(postMeta.postType);
1107
+ const hasMetaKey = source.includes(postMeta.metaKey);
1108
+ const hasSchemaFile = source.includes(postMeta.schemaFile);
1109
+ const hasRestExposure = source.includes("'show_in_rest'");
1110
+ return createDoctorCheck(`Post meta PHP ${postMeta.slug}`, hasRegisterPostMeta && hasPostType && hasMetaKey && hasSchemaFile && hasRestExposure ? "pass" : "fail", hasRegisterPostMeta && hasPostType && hasMetaKey && hasSchemaFile && hasRestExposure ? "Post meta registration, schema path, and REST exposure flag are wired" : "Missing register_post_meta, post type, meta key, schema path, or show_in_rest wiring");
1015
1111
  }
1016
- function getWorkspaceFeatureDoctorChecks(workspace, inventory) {
1112
+ function getWorkspacePostMetaDoctorChecks(workspace, postMetaEntries) {
1017
1113
  const checks = [];
1018
- if (inventory.restResources.some((restResource) => !isManualRestResource(restResource))) {
1019
- checks.push(checkWorkspaceRestResourceBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1020
- }
1021
- for (const restResource of inventory.restResources) {
1022
- checks.push(checkWorkspaceRestResourceConfig(restResource));
1023
- checks.push(checkExistingFiles(workspace.projectDir, `REST resource ${restResource.slug}`, getWorkspaceRestResourceRequiredFiles(restResource)));
1024
- }
1025
- if (inventory.postMeta.length > 0) {
1114
+ if (postMetaEntries.length > 0) {
1026
1115
  checks.push(checkWorkspacePostMetaBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1027
1116
  }
1028
- for (const postMeta of inventory.postMeta) {
1117
+ for (const postMeta of postMetaEntries) {
1029
1118
  checks.push(checkWorkspacePostMetaConfig(postMeta));
1030
1119
  checks.push(checkExistingFiles(workspace.projectDir, `Post meta ${postMeta.slug}`, getWorkspacePostMetaRequiredFiles(postMeta)));
1031
1120
  checks.push(checkWorkspacePostMetaPhp(workspace.projectDir, postMeta));
1032
1121
  }
1033
- if (inventory.abilities.length > 0) {
1034
- checks.push(checkWorkspaceAbilityBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1035
- checks.push(checkWorkspaceAbilityIndex(workspace.projectDir, inventory.abilities));
1122
+ return checks;
1123
+ }
1124
+
1125
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features-rest.ts
1126
+ import fs12 from "fs";
1127
+ import path12 from "path";
1128
+ function isManualRestResource(restResource) {
1129
+ return restResource.mode === "manual";
1130
+ }
1131
+ function getWorkspaceRestResourceRequiredFiles(restResource) {
1132
+ const schemaNames = new Set;
1133
+ if (isManualRestResource(restResource)) {
1134
+ schemaNames.add("query");
1135
+ if (restResource.bodyTypeName) {
1136
+ schemaNames.add("request");
1137
+ }
1138
+ schemaNames.add("response");
1139
+ return Array.from(new Set([
1140
+ restResource.apiFile,
1141
+ ...Array.from(schemaNames, (schemaName) => path12.join(path12.dirname(restResource.typesFile), "api-schemas", `${schemaName}.schema.json`)),
1142
+ restResource.clientFile,
1143
+ restResource.openApiFile,
1144
+ restResource.typesFile,
1145
+ restResource.validatorsFile
1146
+ ]));
1036
1147
  }
1037
- for (const ability of inventory.abilities) {
1038
- checks.push(checkWorkspaceAbilityConfig(workspace.projectDir, ability));
1039
- checks.push(checkExistingFiles(workspace.projectDir, `Ability ${ability.slug}`, getWorkspaceAbilityRequiredFiles(ability)));
1148
+ if (restResource.methods.includes("list")) {
1149
+ schemaNames.add("list-query");
1150
+ schemaNames.add("list-response");
1040
1151
  }
1041
- if (inventory.aiFeatures.length > 0) {
1042
- checks.push(checkWorkspaceAiFeatureBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1152
+ if (restResource.methods.includes("read")) {
1153
+ schemaNames.add("read-query");
1154
+ schemaNames.add("read-response");
1043
1155
  }
1044
- for (const aiFeature of inventory.aiFeatures) {
1045
- checks.push(checkWorkspaceAiFeatureConfig(aiFeature));
1046
- checks.push(checkExistingFiles(workspace.projectDir, `AI feature ${aiFeature.slug}`, getWorkspaceAiFeatureRequiredFiles(aiFeature)));
1156
+ if (restResource.methods.includes("create")) {
1157
+ schemaNames.add("create-request");
1158
+ schemaNames.add("create-response");
1047
1159
  }
1048
- if (inventory.editorPlugins.length > 0) {
1049
- checks.push(checkWorkspaceEditorPluginBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1050
- checks.push(checkWorkspaceEditorPluginIndex(workspace.projectDir, inventory.editorPlugins));
1160
+ if (restResource.methods.includes("update")) {
1161
+ schemaNames.add("update-query");
1162
+ schemaNames.add("update-request");
1163
+ schemaNames.add("update-response");
1051
1164
  }
1052
- for (const editorPlugin of inventory.editorPlugins) {
1053
- checks.push(checkExistingFiles(workspace.projectDir, `Editor plugin ${editorPlugin.slug}`, getWorkspaceEditorPluginRequiredFiles(editorPlugin)));
1054
- checks.push(checkWorkspaceEditorPluginConfig(editorPlugin));
1165
+ if (restResource.methods.includes("delete")) {
1166
+ schemaNames.add("delete-query");
1167
+ schemaNames.add("delete-response");
1055
1168
  }
1056
- if (inventory.adminViews.length > 0) {
1057
- checks.push(checkWorkspaceAdminViewBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1058
- checks.push(checkWorkspaceAdminViewIndex(workspace.projectDir, inventory.adminViews));
1169
+ return Array.from(new Set([
1170
+ restResource.apiFile,
1171
+ ...Array.from(schemaNames, (schemaName) => path12.join(path12.dirname(restResource.typesFile), "api-schemas", `${schemaName}.schema.json`)),
1172
+ restResource.clientFile,
1173
+ ...restResource.dataFile ? [restResource.dataFile] : [],
1174
+ restResource.openApiFile,
1175
+ ...restResource.phpFile ? [restResource.phpFile] : [],
1176
+ restResource.typesFile,
1177
+ restResource.validatorsFile
1178
+ ]));
1179
+ }
1180
+ function checkWorkspaceRestResourceConfig(restResource) {
1181
+ const hasNamespace = REST_RESOURCE_NAMESPACE_PATTERN.test(restResource.namespace);
1182
+ if (isManualRestResource(restResource)) {
1183
+ const hasAuth = restResource.auth == null || MANUAL_REST_CONTRACT_AUTH_IDS.includes(restResource.auth);
1184
+ const hasMethod = typeof restResource.method === "string" && MANUAL_REST_CONTRACT_HTTP_METHOD_IDS.includes(restResource.method);
1185
+ const hasPathPattern = typeof restResource.pathPattern === "string" && restResource.pathPattern.startsWith("/") && restResource.pathPattern.length > 1;
1186
+ return createDoctorCheck(`REST resource config ${restResource.slug}`, hasNamespace && hasAuth && hasMethod && hasPathPattern ? "pass" : "fail", hasNamespace && hasAuth && hasMethod && hasPathPattern ? `Manual REST contract ${restResource.method} /${restResource.namespace}${restResource.pathPattern}` : "Manual REST contract namespace, auth, method, or path pattern is invalid");
1059
1187
  }
1060
- for (const adminView of inventory.adminViews) {
1061
- checks.push(checkWorkspaceAdminViewConfig(adminView, inventory));
1062
- checks.push(checkExistingFiles(workspace.projectDir, `Admin view ${adminView.slug}`, getWorkspaceAdminViewRequiredFiles(adminView)));
1063
- checks.push(checkWorkspaceAdminViewPhp(workspace.projectDir, adminView));
1188
+ const hasMethods = restResource.methods.length > 0 && restResource.methods.every((method) => REST_RESOURCE_METHOD_IDS.includes(method));
1189
+ const hasGeneratedFiles = typeof restResource.dataFile === "string" && restResource.dataFile.length > 0 && typeof restResource.phpFile === "string" && restResource.phpFile.length > 0;
1190
+ const hasRoutePattern = restResource.routePattern == null || typeof restResource.routePattern === "string" && restResource.routePattern.startsWith("/") && restResource.routePattern.length > 1 && !/\s/u.test(restResource.routePattern) && isGeneratedRestResourceRoutePatternCompatible(restResource.routePattern);
1191
+ return createDoctorCheck(`REST resource config ${restResource.slug}`, hasNamespace && hasMethods && hasGeneratedFiles && hasRoutePattern ? "pass" : "fail", hasNamespace && hasMethods && hasGeneratedFiles && hasRoutePattern ? `REST resource namespace ${restResource.namespace} with methods ${restResource.methods.join(", ")}` : "REST resource namespace, methods, dataFile, phpFile, or routePattern are invalid");
1192
+ }
1193
+ function checkWorkspaceRestResourceBootstrap(projectDir, packageName, phpPrefix) {
1194
+ const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
1195
+ if (!fs12.existsSync(bootstrapPath)) {
1196
+ return createDoctorCheck("REST resource bootstrap", "fail", `Missing ${path12.basename(bootstrapPath)}`);
1197
+ }
1198
+ const source = fs12.readFileSync(bootstrapPath, "utf8");
1199
+ const registerFunctionName = `${phpPrefix}_register_rest_resources`;
1200
+ const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
1201
+ const hasServerGlob = source.includes(WORKSPACE_REST_RESOURCE_GLOB);
1202
+ const hasRegisterHook = source.includes(registerHook);
1203
+ return createDoctorCheck("REST resource bootstrap", hasServerGlob && hasRegisterHook ? "pass" : "fail", hasServerGlob && hasRegisterHook ? "REST resource PHP loader hook is present" : "Missing REST resource PHP require glob or init hook");
1204
+ }
1205
+ function getWorkspaceRestResourceDoctorChecks(workspace, restResources) {
1206
+ const checks = [];
1207
+ if (restResources.some((restResource) => !isManualRestResource(restResource))) {
1208
+ checks.push(checkWorkspaceRestResourceBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1209
+ }
1210
+ for (const restResource of restResources) {
1211
+ checks.push(checkWorkspaceRestResourceConfig(restResource));
1212
+ checks.push(checkExistingFiles(workspace.projectDir, `REST resource ${restResource.slug}`, getWorkspaceRestResourceRequiredFiles(restResource)));
1064
1213
  }
1065
1214
  return checks;
1066
1215
  }
1067
1216
 
1217
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features.ts
1218
+ function getWorkspaceFeatureDoctorChecks(workspace, inventory) {
1219
+ return [
1220
+ ...getWorkspaceRestResourceDoctorChecks(workspace, inventory.restResources),
1221
+ ...getWorkspacePostMetaDoctorChecks(workspace, inventory.postMeta),
1222
+ ...getWorkspaceAbilityDoctorChecks(workspace, inventory.abilities),
1223
+ ...getWorkspaceAiFeatureDoctorChecks(workspace, inventory.aiFeatures),
1224
+ ...getWorkspaceEditorPluginDoctorChecks(workspace, inventory.editorPlugins),
1225
+ ...getWorkspaceAdminViewDoctorChecks(workspace, inventory)
1226
+ ];
1227
+ }
1228
+
1068
1229
  // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-package.ts
1069
- import path6 from "path";
1230
+ import path13 from "path";
1070
1231
  async function prepareWorkspacePackageDoctorSnapshot(workspace, packageJson) {
1071
1232
  const packageName = packageJson.name;
1072
1233
  const bootstrapRelativePath = getWorkspaceBootstrapRelativePath(typeof packageName === "string" && packageName.length > 0 ? packageName : workspace.packageName);
1073
- const migrationConfigRelativePath = path6.join("src", "migrations", "config.ts");
1234
+ const migrationConfigRelativePath = path13.join("src", "migrations", "config.ts");
1074
1235
  const [bootstrapExists, migrationConfigExists] = await Promise.all([
1075
- pathExists(path6.join(workspace.projectDir, bootstrapRelativePath)),
1076
- pathExists(path6.join(workspace.projectDir, migrationConfigRelativePath))
1236
+ pathExists(path13.join(workspace.projectDir, bootstrapRelativePath)),
1237
+ pathExists(path13.join(workspace.projectDir, migrationConfigRelativePath))
1077
1238
  ]);
1078
1239
  return {
1079
1240
  bootstrapExists,
@@ -1182,23 +1343,88 @@ async function getWorkspaceDoctorChecks(cwd) {
1182
1343
  }
1183
1344
 
1184
1345
  // ../wp-typia-project-tools/src/runtime/cli-doctor.ts
1346
+ var DEFAULT_DOCTOR_EXIT_POLICY = "strict";
1347
+ var defaultDoctorLinePrinter = (line) => {
1348
+ process.stdout.write(`${line}
1349
+ `);
1350
+ };
1351
+ function renderDefaultDoctorCheckLine(check) {
1352
+ defaultDoctorLinePrinter(formatDoctorCheckLine(check));
1353
+ }
1354
+ function renderDefaultDoctorSummaryLine(summaryLine) {
1355
+ defaultDoctorLinePrinter(summaryLine);
1356
+ }
1357
+ function annotateDoctorChecks(checks, scope) {
1358
+ return checks.map((check) => ({
1359
+ ...check,
1360
+ scope: check.scope ?? scope
1361
+ }));
1362
+ }
1363
+ function resolveDoctorExitPolicy(options) {
1364
+ return options.exitPolicy ?? DEFAULT_DOCTOR_EXIT_POLICY;
1365
+ }
1366
+ function doesCheckContributeToExit(check, exitPolicy) {
1367
+ if (check.status !== "fail") {
1368
+ return false;
1369
+ }
1370
+ if (exitPolicy === "strict") {
1371
+ return true;
1372
+ }
1373
+ return check.scope === "workspace";
1374
+ }
1375
+ function toFailureSummary(check, severity) {
1376
+ return {
1377
+ ...check.code ? { code: check.code } : {},
1378
+ label: check.label,
1379
+ scope: check.scope ?? "unknown",
1380
+ severity
1381
+ };
1382
+ }
1185
1383
  async function getDoctorChecks(cwd) {
1186
1384
  return [
1187
- ...await getEnvironmentDoctorChecks(cwd),
1188
- ...await getWorkspaceDoctorChecks(cwd)
1385
+ ...annotateDoctorChecks(await getEnvironmentDoctorChecks(cwd), "environment"),
1386
+ ...annotateDoctorChecks(await getWorkspaceDoctorChecks(cwd), "workspace")
1189
1387
  ];
1190
1388
  }
1389
+ function getDoctorExitFailureChecks(checks, options = {}) {
1390
+ const exitPolicy = resolveDoctorExitPolicy(options);
1391
+ return checks.filter((check) => doesCheckContributeToExit(check, exitPolicy));
1392
+ }
1393
+ function getDoctorExitFailureDetailLines(checks, options = {}) {
1394
+ return getDoctorExitFailureChecks(checks, options).map((check) => `${check.label}: ${check.detail}`);
1395
+ }
1396
+ function createDoctorRunSummary(checks, options = {}) {
1397
+ const exitPolicy = resolveDoctorExitPolicy(options);
1398
+ const failedChecks = checks.filter((check) => check.status === "fail");
1399
+ const exitFailureChecks = failedChecks.filter((check) => doesCheckContributeToExit(check, exitPolicy));
1400
+ const advisoryFailureChecks = failedChecks.filter((check) => !doesCheckContributeToExit(check, exitPolicy));
1401
+ const warnings = checks.filter((check) => check.status === "warn").length;
1402
+ return {
1403
+ advisoryFailureCount: advisoryFailureChecks.length,
1404
+ advisoryFailures: advisoryFailureChecks.map((check) => toFailureSummary(check, "advisory")),
1405
+ exitCode: exitFailureChecks.length > 0 ? 1 : 0,
1406
+ exitFailureCount: exitFailureChecks.length,
1407
+ exitFailures: exitFailureChecks.map((check) => toFailureSummary(check, "error")),
1408
+ exitPolicy,
1409
+ failed: failedChecks.length,
1410
+ passed: checks.length - failedChecks.length - warnings,
1411
+ total: checks.length,
1412
+ warnings
1413
+ };
1414
+ }
1191
1415
  async function runDoctor(cwd, options = {}) {
1192
- const renderLine = options.renderLine ?? ((check) => console.log(formatDoctorCheckLine(check)));
1416
+ const exitPolicy = resolveDoctorExitPolicy(options);
1417
+ const renderLine = options.renderLine ?? renderDefaultDoctorCheckLine;
1193
1418
  const renderSummaryLine = options.renderSummaryLine ?? (options.renderLine ? () => {
1194
1419
  return;
1195
- } : (summaryLine) => console.log(summaryLine));
1420
+ } : renderDefaultDoctorSummaryLine);
1196
1421
  const checks = await getDoctorChecks(cwd);
1197
1422
  for (const check of checks) {
1198
1423
  renderLine(check);
1199
1424
  }
1200
- renderSummaryLine(formatDoctorSummaryLine(checks));
1201
- const failureDetailLines = getDoctorFailureDetailLines(checks);
1425
+ const exitFailureChecks = getDoctorExitFailureChecks(checks, { exitPolicy });
1426
+ renderSummaryLine(formatDoctorSummaryLine(checks, { exitFailureChecks }));
1427
+ const failureDetailLines = getDoctorExitFailureDetailLines(checks, { exitPolicy });
1202
1428
  if (failureDetailLines.length > 0) {
1203
1429
  throw createCliCommandError({
1204
1430
  code: CLI_DIAGNOSTIC_CODES.DOCTOR_CHECK_FAILED,
@@ -1211,7 +1437,10 @@ async function runDoctor(cwd, options = {}) {
1211
1437
  }
1212
1438
  export {
1213
1439
  runDoctor,
1214
- getDoctorChecks
1440
+ getDoctorExitFailureDetailLines,
1441
+ getDoctorExitFailureChecks,
1442
+ getDoctorChecks,
1443
+ createDoctorRunSummary
1215
1444
  };
1216
1445
 
1217
- //# debugId=A0E15D6F99F87A7464756E2164756E21
1446
+ //# debugId=7D607F817A3CF55764756E2164756E21