wp-typia 0.23.0 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +2 -1
  2. package/bin/routing-metadata.generated.js +4 -0
  3. package/dist-bunli/.bunli/commands.gen.js +4103 -3086
  4. package/dist-bunli/{cli-hhp1d348.js → cli-1170yyve.js} +8 -7
  5. package/dist-bunli/{cli-qse6myha.js → cli-8hxf9qw6.js} +11 -3
  6. package/dist-bunli/{cli-8reep89s.js → cli-9fx0qgb7.js} +2 -2
  7. package/dist-bunli/{cli-add-21bvpfgw.js → cli-add-xjaaa01x.js} +1560 -1525
  8. package/dist-bunli/{cli-52ke0ptp.js → cli-am5x7tb4.js} +8 -2
  9. package/dist-bunli/cli-ccax7s0s.js +34 -0
  10. package/dist-bunli/{cli-diagnostics-5dvztm7q.js → cli-diagnostics-10drxh34.js} +1 -1
  11. package/dist-bunli/{cli-doctor-wy2yjsge.js → cli-doctor-19e8313m.js} +602 -459
  12. package/dist-bunli/{cli-2rqf6t0b.js → cli-e4bwd81c.js} +8 -11
  13. package/dist-bunli/{cli-9npd9was.js → cli-epsczb1c.js} +12 -10
  14. package/dist-bunli/{cli-agywa5n6.js → cli-fp16mntv.js} +8 -4
  15. package/dist-bunli/{cli-init-xnsbxncv.js → cli-init-2b4yn2cc.js} +14 -10
  16. package/dist-bunli/{cli-ts9thts5.js → cli-k5q5v8g6.js} +184 -162
  17. package/dist-bunli/{cli-c2acv5dv.js → cli-nvs5atj1.js} +2 -2
  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-4tjw4jk5.js} +27 -15
  21. package/dist-bunli/{cli-templates-hc71dfc2.js → cli-templates-g8t4fm11.js} +3 -2
  22. package/dist-bunli/{cli-p95wr1q8.js → cli-tq730sqt.js} +6 -3
  23. package/dist-bunli/{cli-1meywwsy.js → cli-y7w3pybs.js} +848 -246
  24. package/dist-bunli/{cli-z5qkx2pn.js → cli-ymecd15q.js} +37 -10
  25. package/dist-bunli/cli.js +4 -4
  26. package/dist-bunli/{command-list-aqrkx021.js → command-list-vme7dr5v.js} +81 -45
  27. package/dist-bunli/{create-template-validation-rtec5sng.js → create-template-validation-4fr851vg.js} +5 -4
  28. package/dist-bunli/{migrations-bx0yvc2v.js → migrations-pb5vvtdp.js} +9 -8
  29. package/dist-bunli/node-cli.js +399 -317
  30. package/dist-bunli/{workspace-project-csnnggz6.js → workspace-project-gmv2a71z.js} +4 -3
  31. package/package.json +2 -2
@@ -9,11 +9,11 @@ import {
9
9
  import {
10
10
  getBuiltInTemplateLayerDirs,
11
11
  isOmittableBuiltInTemplateLayerDir
12
- } from "./cli-c2acv5dv.js";
12
+ } from "./cli-nvs5atj1.js";
13
13
  import {
14
14
  isBuiltInTemplateId,
15
15
  listTemplates
16
- } from "./cli-qse6myha.js";
16
+ } from "./cli-8hxf9qw6.js";
17
17
  import {
18
18
  EDITOR_PLUGIN_SLOT_IDS,
19
19
  HOOKED_BLOCK_ANCHOR_PATTERN,
@@ -28,7 +28,7 @@ import {
28
28
  pathExists,
29
29
  readWorkspaceInventoryAsync,
30
30
  resolveEditorPluginSlotAlias
31
- } from "./cli-ts9thts5.js";
31
+ } from "./cli-k5q5v8g6.js";
32
32
  import"./cli-cvxvcw7c.js";
33
33
  import"./cli-t73q5aqz.js";
34
34
  import"./cli-43mx1vfb.js";
@@ -36,16 +36,18 @@ import {
36
36
  CLI_DIAGNOSTIC_CODES,
37
37
  createCliCommandError,
38
38
  formatDoctorCheckLine,
39
- formatDoctorSummaryLine,
40
- getDoctorFailureDetailLines
41
- } from "./cli-p95wr1q8.js";
39
+ formatDoctorSummaryLine
40
+ } from "./cli-tq730sqt.js";
42
41
  import {
43
42
  WORKSPACE_TEMPLATE_PACKAGE,
44
43
  getInvalidWorkspaceProjectReason,
45
44
  parseWorkspacePackageJson,
46
45
  tryResolveWorkspaceProject
47
- } from "./cli-hhp1d348.js";
48
- import"./cli-52ke0ptp.js";
46
+ } from "./cli-1170yyve.js";
47
+ import"./cli-am5x7tb4.js";
48
+ import {
49
+ readJsonFileSync
50
+ } from "./cli-ccax7s0s.js";
49
51
  import"./cli-xnn9xjcy.js";
50
52
 
51
53
  // ../wp-typia-project-tools/src/runtime/cli-doctor-environment.ts
@@ -229,7 +231,9 @@ function checkWorkspaceBindingTarget(projectDir, workspace, registeredBlockSlugs
229
231
  const blockJsonPath = path3.join(projectDir, blockJsonRelativePath);
230
232
  const issues = [];
231
233
  try {
232
- const blockJson = parseScaffoldBlockMetadata(JSON.parse(fs3.readFileSync(blockJsonPath, "utf8")));
234
+ const blockJson = parseScaffoldBlockMetadata(readJsonFileSync(blockJsonPath, {
235
+ context: "workspace block metadata"
236
+ }));
233
237
  const attributes = blockJson.attributes;
234
238
  if (!attributes || typeof attributes !== "object" || Array.isArray(attributes)) {
235
239
  issues.push(`${blockJsonRelativePath} must define an attributes object`);
@@ -298,18 +302,125 @@ function getWorkspaceBindingDoctorChecks(workspace, inventory) {
298
302
  return checks;
299
303
  }
300
304
 
301
- // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-blocks.ts
305
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-block-addons.ts
302
306
  import fs4 from "fs";
303
307
  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
308
  var WORKSPACE_VARIATIONS_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceVariations\s*\}\s*from\s*["']\.\/variations["']\s*;?\s*$/mu;
308
309
  var WORKSPACE_VARIATIONS_CALL_PATTERN = /registerWorkspaceVariations\s*\(\s*\)\s*;?/u;
309
310
  var WORKSPACE_BLOCK_STYLES_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceBlockStyles\s*\}\s*from\s*["']\.\/styles["']\s*;?\s*$/mu;
310
311
  var WORKSPACE_BLOCK_STYLES_CALL_PATTERN = /registerWorkspaceBlockStyles\s*\(\s*\)\s*;?/u;
311
312
  var WORKSPACE_BLOCK_TRANSFORMS_IMPORT_PATTERN = /^\s*import\s*\{\s*applyWorkspaceBlockTransforms\s*\}\s*from\s*["']\.\/transforms["']\s*;?\s*$/mu;
312
313
  var WORKSPACE_BLOCK_TRANSFORMS_CALL_PATTERN = /applyWorkspaceBlockTransforms\s*\(\s*registration\s*\.\s*settings\s*\)\s*;?/u;
314
+ function checkWorkspacePatternBootstrap(projectDir, packageName) {
315
+ const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
316
+ if (!fs4.existsSync(bootstrapPath)) {
317
+ return createDoctorCheck("Pattern bootstrap", "fail", `Missing ${path4.basename(bootstrapPath)}`);
318
+ }
319
+ const source = fs4.readFileSync(bootstrapPath, "utf8");
320
+ const hasCategoryAnchor = source.includes("register_block_pattern_category");
321
+ const hasPatternGlob = source.includes("/src/patterns/*.php");
322
+ 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");
323
+ }
324
+ function checkVariationEntrypoint(projectDir, blockSlug) {
325
+ const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
326
+ if (!fs4.existsSync(entryPath)) {
327
+ return createDoctorCheck(`Variation entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
328
+ }
329
+ const source = fs4.readFileSync(entryPath, "utf8");
330
+ const hasImport = hasUncommentedPattern(source, WORKSPACE_VARIATIONS_IMPORT_PATTERN);
331
+ const hasCall = hasExecutablePattern(source, WORKSPACE_VARIATIONS_CALL_PATTERN);
332
+ return createDoctorCheck(`Variation entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Variations registration hook is present" : "Missing ./variations import or registerWorkspaceVariations() call");
333
+ }
334
+ function checkBlockStyleEntrypoint(projectDir, blockSlug) {
335
+ const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
336
+ if (!fs4.existsSync(entryPath)) {
337
+ return createDoctorCheck(`Block style entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
338
+ }
339
+ const source = fs4.readFileSync(entryPath, "utf8");
340
+ const hasImport = hasUncommentedPattern(source, WORKSPACE_BLOCK_STYLES_IMPORT_PATTERN);
341
+ const hasCall = hasExecutablePattern(source, WORKSPACE_BLOCK_STYLES_CALL_PATTERN);
342
+ return createDoctorCheck(`Block style entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Block style registration hook is present" : "Missing ./styles import or registerWorkspaceBlockStyles() call");
343
+ }
344
+ function checkBlockTransformEntrypoint(projectDir, blockSlug) {
345
+ const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
346
+ if (!fs4.existsSync(entryPath)) {
347
+ return createDoctorCheck(`Block transform entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
348
+ }
349
+ const source = fs4.readFileSync(entryPath, "utf8");
350
+ const hasImport = hasUncommentedPattern(source, WORKSPACE_BLOCK_TRANSFORMS_IMPORT_PATTERN);
351
+ const hasCall = hasExecutablePattern(source, WORKSPACE_BLOCK_TRANSFORMS_CALL_PATTERN);
352
+ 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");
353
+ }
354
+ function checkBlockTransformConfig(workspace, transform) {
355
+ const expectedTo = `${workspace.workspace.namespace}/${transform.block}`;
356
+ const issues = [];
357
+ if (!WORKSPACE_FULL_BLOCK_NAME_PATTERN.test(transform.from)) {
358
+ issues.push("from must use full namespace/block format");
359
+ }
360
+ if (transform.to !== expectedTo) {
361
+ issues.push(`to must equal "${expectedTo}" for workspace block "${transform.block}"`);
362
+ }
363
+ 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("; "));
364
+ }
365
+ function getWorkspaceBlockAddonDoctorChecks(workspace, inventory, registeredBlockSlugs) {
366
+ const checks = [];
367
+ const variationTargetBlocks = new Set;
368
+ for (const variation of inventory.variations) {
369
+ if (!registeredBlockSlugs.has(variation.block)) {
370
+ checks.push(createDoctorCheck(`Variation ${variation.block}/${variation.slug}`, "fail", `Variation references unknown block "${variation.block}"`));
371
+ continue;
372
+ }
373
+ variationTargetBlocks.add(variation.block);
374
+ checks.push(checkExistingFiles(workspace.projectDir, `Variation ${variation.block}/${variation.slug}`, [variation.file]));
375
+ }
376
+ for (const blockSlug of variationTargetBlocks) {
377
+ checks.push(checkVariationEntrypoint(workspace.projectDir, blockSlug));
378
+ }
379
+ const blockStyleTargetBlocks = new Set;
380
+ for (const blockStyle of inventory.blockStyles) {
381
+ if (!registeredBlockSlugs.has(blockStyle.block)) {
382
+ checks.push(createDoctorCheck(`Block style ${blockStyle.block}/${blockStyle.slug}`, "fail", `Block style references unknown block "${blockStyle.block}"`));
383
+ continue;
384
+ }
385
+ blockStyleTargetBlocks.add(blockStyle.block);
386
+ checks.push(checkExistingFiles(workspace.projectDir, `Block style ${blockStyle.block}/${blockStyle.slug}`, [blockStyle.file]));
387
+ }
388
+ for (const blockSlug of blockStyleTargetBlocks) {
389
+ checks.push(checkExistingFiles(workspace.projectDir, `Block style registry ${blockSlug}`, [
390
+ path4.join("src", "blocks", blockSlug, "styles", "index.ts")
391
+ ]));
392
+ checks.push(checkBlockStyleEntrypoint(workspace.projectDir, blockSlug));
393
+ }
394
+ const blockTransformTargetBlocks = new Set;
395
+ for (const blockTransform of inventory.blockTransforms) {
396
+ if (!registeredBlockSlugs.has(blockTransform.block)) {
397
+ checks.push(createDoctorCheck(`Block transform ${blockTransform.block}/${blockTransform.slug}`, "fail", `Block transform references unknown block "${blockTransform.block}"`));
398
+ continue;
399
+ }
400
+ blockTransformTargetBlocks.add(blockTransform.block);
401
+ checks.push(checkBlockTransformConfig(workspace, blockTransform));
402
+ checks.push(checkExistingFiles(workspace.projectDir, `Block transform ${blockTransform.block}/${blockTransform.slug}`, [blockTransform.file]));
403
+ }
404
+ for (const blockSlug of blockTransformTargetBlocks) {
405
+ checks.push(checkExistingFiles(workspace.projectDir, `Block transform registry ${blockSlug}`, [
406
+ path4.join("src", "blocks", blockSlug, "transforms", "index.ts")
407
+ ]));
408
+ checks.push(checkBlockTransformEntrypoint(workspace.projectDir, blockSlug));
409
+ }
410
+ const shouldCheckPatternBootstrap = inventory.patterns.length > 0 || fs4.existsSync(path4.join(workspace.projectDir, "src", "patterns"));
411
+ if (shouldCheckPatternBootstrap) {
412
+ checks.push(checkWorkspacePatternBootstrap(workspace.projectDir, workspace.packageName));
413
+ }
414
+ for (const pattern of inventory.patterns) {
415
+ checks.push(checkExistingFiles(workspace.projectDir, `Pattern ${pattern.slug}`, [pattern.file]));
416
+ }
417
+ return checks;
418
+ }
419
+
420
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-block-iframe.ts
421
+ import fs5 from "fs";
422
+ import path5 from "path";
423
+ import { parseScaffoldBlockMetadata as parseScaffoldBlockMetadata2 } from "@wp-typia/block-runtime/blocks";
313
424
  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
425
  var WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES = {
315
426
  API_VERSION: "wp-typia.workspace.block.iframe.api-version",
@@ -340,7 +451,7 @@ var WORKSPACE_BLOCK_LOCAL_STYLE_FILES = [
340
451
  var WORKSPACE_BLOCK_IFRAME_GLOBAL_DOM_PATTERN = /\b(?:document|window)\b|\b(?:parent|top)\b(?!\s*:)/gu;
341
452
  var WORKSPACE_BLOCK_PROPS_PATTERN = /\buse(?:Block|InnerBlocks)Props(?:\.save)?\s*\(/u;
342
453
  function normalizePathSeparators(relativePath) {
343
- return relativePath.split(path4.sep).join("/");
454
+ return relativePath.split(path5.sep).join("/");
344
455
  }
345
456
  function hasRegisteredBlockAsset(value) {
346
457
  if (typeof value === "string") {
@@ -352,16 +463,18 @@ function hasRegisteredBlockAsset(value) {
352
463
  return false;
353
464
  }
354
465
  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)) {
466
+ const blockJsonRelativePath = path5.join("src", "blocks", blockSlug, "block.json");
467
+ const blockJsonPath = path5.join(projectDir, blockJsonRelativePath);
468
+ if (!fs5.existsSync(blockJsonPath)) {
358
469
  return {
359
470
  blockJsonRelativePath,
360
471
  error: `Missing ${blockJsonRelativePath}`
361
472
  };
362
473
  }
363
474
  try {
364
- const document = parseScaffoldBlockMetadata2(JSON.parse(fs4.readFileSync(blockJsonPath, "utf8")));
475
+ const document = parseScaffoldBlockMetadata2(readJsonFileSync(blockJsonPath, {
476
+ context: "workspace block metadata"
477
+ }));
365
478
  return {
366
479
  blockJsonRelativePath,
367
480
  document
@@ -378,8 +491,8 @@ function isWorkspaceBlockEditorSource(relativePath) {
378
491
  return false;
379
492
  }
380
493
  const normalizedRelativePath = normalizePathSeparators(relativePath);
381
- const normalizedDirName = path4.posix.dirname(normalizedRelativePath);
382
- const normalizedBaseName = path4.posix.basename(normalizedRelativePath, path4.posix.extname(normalizedRelativePath));
494
+ const normalizedDirName = path5.posix.dirname(normalizedRelativePath);
495
+ const normalizedBaseName = path5.posix.basename(normalizedRelativePath, path5.posix.extname(normalizedRelativePath));
383
496
  if (WORKSPACE_BLOCK_EDITOR_SOURCE_BASENAMES.has(normalizedBaseName)) {
384
497
  return true;
385
498
  }
@@ -387,12 +500,12 @@ function isWorkspaceBlockEditorSource(relativePath) {
387
500
  return pathSegments.some((segment) => WORKSPACE_BLOCK_EDITOR_SOURCE_DIRECTORIES.has(segment));
388
501
  }
389
502
  function isWorkspaceBlockSaveSource(relativePath) {
390
- const normalizedBaseName = path4.basename(relativePath, path4.extname(relativePath));
503
+ const normalizedBaseName = path5.basename(relativePath, path5.extname(relativePath));
391
504
  return normalizedBaseName === "save";
392
505
  }
393
506
  function collectWorkspaceBlockEditorSources(projectDir, blockSlug) {
394
- const blockDir = path4.join(projectDir, "src", "blocks", blockSlug);
395
- if (!fs4.existsSync(blockDir)) {
507
+ const blockDir = path5.join(projectDir, "src", "blocks", blockSlug);
508
+ if (!fs5.existsSync(blockDir)) {
396
509
  return [];
397
510
  }
398
511
  const collected = [];
@@ -402,8 +515,8 @@ function collectWorkspaceBlockEditorSources(projectDir, blockSlug) {
402
515
  if (!currentDir) {
403
516
  continue;
404
517
  }
405
- for (const entry of fs4.readdirSync(currentDir, { withFileTypes: true })) {
406
- const absolutePath = path4.join(currentDir, entry.name);
518
+ for (const entry of fs5.readdirSync(currentDir, { withFileTypes: true })) {
519
+ const absolutePath = path5.join(currentDir, entry.name);
407
520
  if (entry.isDirectory()) {
408
521
  queue.push(absolutePath);
409
522
  continue;
@@ -411,13 +524,13 @@ function collectWorkspaceBlockEditorSources(projectDir, blockSlug) {
411
524
  if (!entry.isFile()) {
412
525
  continue;
413
526
  }
414
- const relativePath = path4.relative(projectDir, absolutePath);
527
+ const relativePath = path5.relative(projectDir, absolutePath);
415
528
  if (!isWorkspaceBlockEditorSource(relativePath)) {
416
529
  continue;
417
530
  }
418
531
  collected.push({
419
532
  relativePath: normalizePathSeparators(relativePath),
420
- source: fs4.readFileSync(absolutePath, "utf8")
533
+ source: fs5.readFileSync(absolutePath, "utf8")
421
534
  });
422
535
  }
423
536
  }
@@ -475,25 +588,60 @@ function findWorkspaceBlockGlobalDomAccesses(editorSources) {
475
588
  return findings;
476
589
  });
477
590
  }
591
+ function getWorkspaceBlockIframeCompatibilityChecks(projectDir, blockSlug) {
592
+ const metadataResult = readWorkspaceBlockIframeMetadata(projectDir, blockSlug);
593
+ if (!metadataResult.document) {
594
+ return [
595
+ createDoctorCheck(`Block iframe/API v3 ${blockSlug}`, "warn", metadataResult.error ?? `Unable to inspect ${metadataResult.blockJsonRelativePath}`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.API_VERSION)
596
+ ];
597
+ }
598
+ const blockJson = metadataResult.document;
599
+ const apiVersion = typeof blockJson.apiVersion === "number" && Number.isFinite(blockJson.apiVersion) ? blockJson.apiVersion : null;
600
+ const blockDir = path5.join(projectDir, "src", "blocks", blockSlug);
601
+ const localStyleFiles = WORKSPACE_BLOCK_LOCAL_STYLE_FILES.filter((fileName) => fs5.existsSync(path5.join(blockDir, fileName))).map((fileName) => normalizePathSeparators(path5.join("src", "blocks", blockSlug, fileName)));
602
+ const hasRegisteredEditorStyles = hasRegisteredBlockAsset(blockJson.style) || hasRegisteredBlockAsset(blockJson.editorStyle);
603
+ const editorSources = collectWorkspaceBlockEditorSources(projectDir, blockSlug);
604
+ const editorWrapperSources = editorSources.filter((source) => !isWorkspaceBlockSaveSource(source.relativePath));
605
+ const globalDomAccesses = findWorkspaceBlockGlobalDomAccesses(editorSources);
606
+ const hasBlockPropsUsage = editorSources.some(({ source }) => hasExecutablePattern(source, WORKSPACE_BLOCK_PROPS_PATTERN));
607
+ const hasEditorBlockPropsUsage = editorWrapperSources.some(({ source }) => hasExecutablePattern(source, WORKSPACE_BLOCK_PROPS_PATTERN));
608
+ const blockWrapperStatus = editorWrapperSources.length === 0 || hasEditorBlockPropsUsage ? "pass" : "warn";
609
+ 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.";
610
+ return [
611
+ 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),
612
+ 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),
613
+ 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),
614
+ createDoctorCheck(`Block iframe wrapper ${blockSlug}`, blockWrapperStatus, blockWrapperDetail, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.BLOCK_PROPS)
615
+ ];
616
+ }
617
+
618
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-block-metadata.ts
619
+ import fs6 from "fs";
620
+ import path6 from "path";
621
+ import { parseScaffoldBlockMetadata as parseScaffoldBlockMetadata3 } from "@wp-typia/block-runtime/blocks";
622
+ var WORKSPACE_COLLECTION_IMPORT_LINE = "import '../../collection';";
623
+ var WORKSPACE_COLLECTION_IMPORT_PATTERN = /^\s*import\s+["']\.\.\/\.\.\/collection["']\s*;?\s*$/m;
478
624
  function getWorkspaceBlockRequiredFiles(block) {
479
- const blockDir = path4.join("src", "blocks", block.slug);
625
+ const blockDir = path6.join("src", "blocks", block.slug);
480
626
  return Array.from(new Set([
481
627
  block.typesFile,
482
628
  block.apiTypesFile,
483
629
  block.openApiFile,
484
- path4.join(blockDir, "index.tsx"),
485
- ...WORKSPACE_GENERATED_BLOCK_ARTIFACTS.map((fileName) => path4.join(blockDir, fileName))
630
+ path6.join(blockDir, "index.tsx"),
631
+ ...WORKSPACE_GENERATED_BLOCK_ARTIFACTS.map((fileName) => path6.join(blockDir, fileName))
486
632
  ].filter((filePath) => typeof filePath === "string")));
487
633
  }
488
634
  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)) {
635
+ const blockJsonRelativePath = path6.join("src", "blocks", block.slug, "block.json");
636
+ const blockJsonPath = path6.join(projectDir, blockJsonRelativePath);
637
+ if (!fs6.existsSync(blockJsonPath)) {
492
638
  return createDoctorCheck(`Block metadata ${block.slug}`, "fail", `Missing ${blockJsonRelativePath}`);
493
639
  }
494
640
  let blockJson;
495
641
  try {
496
- blockJson = parseScaffoldBlockMetadata2(JSON.parse(fs4.readFileSync(blockJsonPath, "utf8")));
642
+ blockJson = parseScaffoldBlockMetadata3(readJsonFileSync(blockJsonPath, {
643
+ context: "workspace block metadata"
644
+ }));
497
645
  } catch (error) {
498
646
  return createDoctorCheck(`Block metadata ${block.slug}`, "fail", error instanceof Error ? error.message : String(error));
499
647
  }
@@ -508,14 +656,16 @@ function checkWorkspaceBlockMetadata(projectDir, workspace, block) {
508
656
  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
657
  }
510
658
  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)) {
659
+ const blockJsonRelativePath = path6.join("src", "blocks", blockSlug, "block.json");
660
+ const blockJsonPath = path6.join(projectDir, blockJsonRelativePath);
661
+ if (!fs6.existsSync(blockJsonPath)) {
514
662
  return createDoctorCheck(`Block hooks ${blockSlug}`, "fail", `Missing ${blockJsonRelativePath}`);
515
663
  }
516
664
  let blockJson;
517
665
  try {
518
- blockJson = parseScaffoldBlockMetadata2(JSON.parse(fs4.readFileSync(blockJsonPath, "utf8")));
666
+ blockJson = parseScaffoldBlockMetadata3(readJsonFileSync(blockJsonPath, {
667
+ context: "workspace block metadata"
668
+ }));
519
669
  } catch (error) {
520
670
  return createDoctorCheck(`Block hooks ${blockSlug}`, "fail", error instanceof Error ? error.message : String(error));
521
671
  }
@@ -531,348 +681,214 @@ function checkWorkspaceBlockHooks(projectDir, blockSlug) {
531
681
  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
682
  }
533
683
  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)) {
684
+ const entryRelativePath = path6.join("src", "blocks", blockSlug, "index.tsx");
685
+ const entryPath = path6.join(projectDir, entryRelativePath);
686
+ if (!fs6.existsSync(entryPath)) {
537
687
  return createDoctorCheck(`Block collection ${blockSlug}`, "fail", `Missing ${entryRelativePath}`);
538
688
  }
539
- const source = fs4.readFileSync(entryPath, "utf8");
689
+ const source = fs6.readFileSync(entryPath, "utf8");
540
690
  const hasCollectionImport = WORKSPACE_COLLECTION_IMPORT_PATTERN.test(source);
541
691
  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
692
  }
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.";
693
+ function getWorkspaceBlockCoreDoctorChecks(workspace, block) {
562
694
  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)
695
+ checkExistingFiles(workspace.projectDir, `Block ${block.slug}`, getWorkspaceBlockRequiredFiles(block)),
696
+ checkWorkspaceBlockMetadata(workspace.projectDir, workspace, block),
697
+ checkWorkspaceBlockHooks(workspace.projectDir, block.slug),
698
+ checkWorkspaceBlockCollectionImport(workspace.projectDir, block.slug)
567
699
  ];
568
700
  }
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)}`);
701
+
702
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-blocks.ts
703
+ function getWorkspaceBlockDoctorChecks(workspace, inventory) {
704
+ const checks = [];
705
+ for (const block of inventory.blocks) {
706
+ checks.push(...getWorkspaceBlockCoreDoctorChecks(workspace, block));
707
+ checks.push(...getWorkspaceBlockIframeCompatibilityChecks(workspace.projectDir, block.slug));
573
708
  }
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");
709
+ const registeredBlockSlugs = new Set(inventory.blocks.map((block) => block.slug));
710
+ checks.push(...getWorkspaceBlockAddonDoctorChecks(workspace, inventory, registeredBlockSlugs));
711
+ return checks;
578
712
  }
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");
713
+
714
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features-abilities.ts
715
+ import fs7 from "fs";
716
+ import path7 from "path";
717
+ function getWorkspaceAbilityRequiredFiles(ability) {
718
+ return Array.from(new Set([
719
+ ability.clientFile,
720
+ ability.configFile,
721
+ ability.dataFile,
722
+ ability.inputSchemaFile,
723
+ ability.outputSchemaFile,
724
+ ability.phpFile,
725
+ ability.typesFile
726
+ ]));
588
727
  }
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)}`);
728
+ function checkWorkspaceAbilityConfig(projectDir, ability) {
729
+ const configPath = path7.join(projectDir, ability.configFile);
730
+ if (!fs7.existsSync(configPath)) {
731
+ return createDoctorCheck(`Ability config ${ability.slug}`, "fail", `Missing ${ability.configFile}`);
593
732
  }
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)}`);
733
+ try {
734
+ const config = readJsonFileSync(configPath, {
735
+ context: "workspace ability config"
736
+ });
737
+ const abilityId = typeof config.abilityId === "string" ? config.abilityId.trim() : "";
738
+ const categorySlug = typeof config.category?.slug === "string" ? config.category.slug.trim() : "";
739
+ const hasValidAbilityId = /^[a-z0-9-]+\/[a-z0-9-]+$/u.test(abilityId);
740
+ const hasValidCategorySlug = /^[a-z0-9-]+$/u.test(categorySlug);
741
+ 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.");
742
+ } catch (error) {
743
+ return createDoctorCheck(`Ability config ${ability.slug}`, "fail", error instanceof Error ? error.message : String(error));
603
744
  }
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
745
  }
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}"`);
746
+ function checkWorkspaceAbilityBootstrap(projectDir, packageName, phpPrefix) {
747
+ const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
748
+ if (!fs7.existsSync(bootstrapPath)) {
749
+ return createDoctorCheck("Ability bootstrap", "fail", `Missing ${path7.basename(bootstrapPath)}`);
617
750
  }
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("; "));
751
+ const source = fs7.readFileSync(bootstrapPath, "utf8");
752
+ const loadFunctionName = `${phpPrefix}_load_workflow_abilities`;
753
+ const enqueueFunctionName = `${phpPrefix}_enqueue_workflow_abilities`;
754
+ const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
755
+ const adminEnqueueHook = `add_action( 'admin_enqueue_scripts', '${enqueueFunctionName}' );`;
756
+ const editorEnqueueHook = `add_action( 'enqueue_block_editor_assets', '${enqueueFunctionName}' );`;
757
+ const hasLoaderHook = source.includes(loadHook);
758
+ const hasAdminEnqueueHook = source.includes(adminEnqueueHook);
759
+ const hasEditorEnqueueHook = source.includes(editorEnqueueHook);
760
+ const hasServerGlob = source.includes(WORKSPACE_ABILITY_GLOB);
761
+ const hasEditorScript = source.includes(WORKSPACE_ABILITY_EDITOR_SCRIPT);
762
+ const hasEditorAsset = source.includes(WORKSPACE_ABILITY_EDITOR_ASSET);
763
+ const hasScriptModuleEnqueue = source.includes("wp_enqueue_script_module");
764
+ return createDoctorCheck("Ability bootstrap", hasLoaderHook && hasAdminEnqueueHook && hasEditorEnqueueHook && hasServerGlob && hasEditorScript && hasEditorAsset && hasScriptModuleEnqueue ? "pass" : "fail", hasLoaderHook && hasAdminEnqueueHook && hasEditorEnqueueHook && hasServerGlob && hasEditorScript && hasEditorAsset && hasScriptModuleEnqueue ? "Ability loader and admin/editor script-module bootstrap hooks are present" : "Missing ability loader hook, script-module enqueue, or build/abilities asset references");
619
765
  }
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));
766
+ function checkWorkspaceAbilityIndex(projectDir, abilities) {
767
+ const indexRelativePath = [
768
+ path7.join("src", "abilities", "index.ts"),
769
+ path7.join("src", "abilities", "index.js")
770
+ ].find((relativePath) => fs7.existsSync(path7.join(projectDir, relativePath)));
771
+ if (!indexRelativePath) {
772
+ return createDoctorCheck("Abilities index", "fail", "Missing src/abilities/index.ts or src/abilities/index.js");
628
773
  }
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));
774
+ const indexPath = path7.join(projectDir, indexRelativePath);
775
+ const source = fs7.readFileSync(indexPath, "utf8");
776
+ const missingExports = abilities.filter((ability) => {
777
+ const exportPattern = new RegExp(`^\\s*export\\s+(?:\\*\\s+from|\\{[^}]+\\}\\s+from)\\s+['"\`]\\./${escapeRegex(ability.slug)}\\/client['"\`]`, "mu");
778
+ return !exportPattern.test(source);
779
+ });
780
+ 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(", ")}`);
781
+ }
782
+ function getWorkspaceAbilityDoctorChecks(workspace, abilities) {
783
+ const checks = [];
784
+ if (abilities.length > 0) {
785
+ checks.push(checkWorkspaceAbilityBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
786
+ checks.push(checkWorkspaceAbilityIndex(workspace.projectDir, abilities));
676
787
  }
677
- for (const pattern of inventory.patterns) {
678
- checks.push(checkExistingFiles(workspace.projectDir, `Pattern ${pattern.slug}`, [pattern.file]));
788
+ for (const ability of abilities) {
789
+ checks.push(checkWorkspaceAbilityConfig(workspace.projectDir, ability));
790
+ checks.push(checkExistingFiles(workspace.projectDir, `Ability ${ability.slug}`, getWorkspaceAbilityRequiredFiles(ability)));
679
791
  }
680
792
  return checks;
681
793
  }
682
794
 
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) {
795
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features-admin-views.ts
796
+ import fs8 from "fs";
797
+ import path8 from "path";
798
+ function getWorkspaceAdminViewRequiredFiles(adminView) {
799
+ const adminViewDir = path8.join("src", "admin-views", adminView.slug);
807
800
  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
801
+ adminView.file,
802
+ adminView.phpFile,
803
+ path8.join(adminViewDir, "Screen.tsx"),
804
+ path8.join(adminViewDir, "config.ts"),
805
+ path8.join(adminViewDir, "data.ts"),
806
+ path8.join(adminViewDir, "style.scss"),
807
+ path8.join(adminViewDir, "types.ts")
815
808
  ]));
816
809
  }
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));
810
+ function checkWorkspaceAdminViewConfig(adminView, inventory) {
811
+ if (adminView.source === undefined) {
812
+ return createDoctorCheck(`Admin view config ${adminView.slug}`, "pass", "Admin view uses a replaceable local fetcher");
831
813
  }
814
+ const source = adminView.source.trim();
815
+ const restSourceMatch = /^rest-resource:([a-z][a-z0-9-]*)$/u.exec(source);
816
+ const coreDataSourceMatch = /^core-data:(postType|taxonomy)\/([a-z0-9][a-z0-9_-]*)$/u.exec(source);
817
+ const restResourceSlug = restSourceMatch?.[1];
818
+ const restResource = restResourceSlug ? inventory.restResources.find((entry) => entry.slug === restResourceSlug) : undefined;
819
+ const isListCapableRestResource = Boolean(restResource?.methods.includes("list"));
820
+ const isManualSettingsRestResource = isAdminViewManualSettingsRestResource(restResource);
821
+ const hasManualSettingsRouteParameters = isManualSettingsRestResource && hasAdminViewManualSettingsRouteParameters(restResource);
822
+ const isValid = isListCapableRestResource || isManualSettingsRestResource && !hasManualSettingsRouteParameters || Boolean(coreDataSourceMatch);
823
+ 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>";
824
+ 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);
832
825
  }
833
- function checkWorkspaceAbilityBootstrap(projectDir, packageName, phpPrefix) {
826
+ function checkWorkspaceAdminViewBootstrap(projectDir, packageName, phpPrefix) {
834
827
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
835
- if (!fs5.existsSync(bootstrapPath)) {
836
- return createDoctorCheck("Ability bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
828
+ if (!fs8.existsSync(bootstrapPath)) {
829
+ return createDoctorCheck("Admin view bootstrap", "fail", `Missing ${path8.basename(bootstrapPath)}`);
837
830
  }
838
- const source = fs5.readFileSync(bootstrapPath, "utf8");
839
- const loadFunctionName = `${phpPrefix}_load_workflow_abilities`;
840
- const enqueueFunctionName = `${phpPrefix}_enqueue_workflow_abilities`;
831
+ const source = fs8.readFileSync(bootstrapPath, "utf8");
832
+ const loadFunctionName = `${phpPrefix}_load_admin_views`;
841
833
  const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
842
- const adminEnqueueHook = `add_action( 'admin_enqueue_scripts', '${enqueueFunctionName}' );`;
843
- const editorEnqueueHook = `add_action( 'enqueue_block_editor_assets', '${enqueueFunctionName}' );`;
844
834
  const hasLoaderHook = source.includes(loadHook);
845
- const hasAdminEnqueueHook = source.includes(adminEnqueueHook);
846
- const hasEditorEnqueueHook = source.includes(editorEnqueueHook);
847
- const hasServerGlob = source.includes(WORKSPACE_ABILITY_GLOB);
848
- const hasEditorScript = source.includes(WORKSPACE_ABILITY_EDITOR_SCRIPT);
849
- const hasEditorAsset = source.includes(WORKSPACE_ABILITY_EDITOR_ASSET);
850
- const hasScriptModuleEnqueue = source.includes("wp_enqueue_script_module");
851
- return createDoctorCheck("Ability bootstrap", hasLoaderHook && hasAdminEnqueueHook && hasEditorEnqueueHook && hasServerGlob && hasEditorScript && hasEditorAsset && hasScriptModuleEnqueue ? "pass" : "fail", hasLoaderHook && hasAdminEnqueueHook && hasEditorEnqueueHook && hasServerGlob && hasEditorScript && hasEditorAsset && hasScriptModuleEnqueue ? "Ability loader and admin/editor script-module bootstrap hooks are present" : "Missing ability loader hook, script-module enqueue, or build/abilities asset references");
835
+ const hasServerGlob = source.includes(WORKSPACE_ADMIN_VIEW_GLOB);
836
+ 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");
852
837
  }
853
- function checkWorkspaceAbilityIndex(projectDir, abilities) {
838
+ function checkWorkspaceAdminViewIndex(projectDir, adminViews) {
854
839
  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)));
840
+ path8.join("src", "admin-views", "index.ts"),
841
+ path8.join("src", "admin-views", "index.js")
842
+ ].find((relativePath) => fs8.existsSync(path8.join(projectDir, relativePath)));
858
843
  if (!indexRelativePath) {
859
- return createDoctorCheck("Abilities index", "fail", "Missing src/abilities/index.ts or src/abilities/index.js");
844
+ return createDoctorCheck("Admin views index", "fail", "Missing src/admin-views/index.ts or src/admin-views/index.js");
860
845
  }
861
- const indexPath = path5.join(projectDir, indexRelativePath);
862
- const source = fs5.readFileSync(indexPath, "utf8");
863
- const missingExports = abilities.filter((ability) => {
864
- const exportPattern = new RegExp(`^\\s*export\\s+(?:\\*\\s+from|\\{[^}]+\\}\\s+from)\\s+['"\`]\\./${escapeRegex(ability.slug)}\\/client['"\`]`, "mu");
865
- return !exportPattern.test(source);
846
+ const indexPath = path8.join(projectDir, indexRelativePath);
847
+ const source = fs8.readFileSync(indexPath, "utf8");
848
+ const missingImports = adminViews.filter((adminView) => {
849
+ const importPattern = new RegExp(`['"\`]\\./${escapeRegex(adminView.slug)}(?:/[^'"\`]*)?['"\`]`, "u");
850
+ return !importPattern.test(source);
866
851
  });
867
- 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(", ")}`);
852
+ 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(", ")}`);
853
+ }
854
+ function checkWorkspaceAdminViewPhp(projectDir, adminView) {
855
+ const phpPath = path8.join(projectDir, adminView.phpFile);
856
+ if (!fs8.existsSync(phpPath)) {
857
+ return createDoctorCheck(`Admin view PHP ${adminView.slug}`, "fail", `Missing ${adminView.phpFile}`);
858
+ }
859
+ const source = fs8.readFileSync(phpPath, "utf8");
860
+ const hasAdminMenu = source.includes("add_submenu_page");
861
+ const hasAdminEnqueue = source.includes("admin_enqueue_scripts");
862
+ const hasScript = source.includes(WORKSPACE_ADMIN_VIEW_SCRIPT);
863
+ const hasAsset = source.includes(WORKSPACE_ADMIN_VIEW_ASSET);
864
+ const hasStyle = source.includes(WORKSPACE_ADMIN_VIEW_STYLE);
865
+ const hasComponentsStyleDependency = /['"]wp-components['"]/u.test(source);
866
+ 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");
868
867
  }
868
+ function getWorkspaceAdminViewDoctorChecks(workspace, inventory) {
869
+ const checks = [];
870
+ if (inventory.adminViews.length > 0) {
871
+ checks.push(checkWorkspaceAdminViewBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
872
+ checks.push(checkWorkspaceAdminViewIndex(workspace.projectDir, inventory.adminViews));
873
+ }
874
+ for (const adminView of inventory.adminViews) {
875
+ checks.push(checkWorkspaceAdminViewConfig(adminView, inventory));
876
+ checks.push(checkExistingFiles(workspace.projectDir, `Admin view ${adminView.slug}`, getWorkspaceAdminViewRequiredFiles(adminView)));
877
+ checks.push(checkWorkspaceAdminViewPhp(workspace.projectDir, adminView));
878
+ }
879
+ return checks;
880
+ }
881
+
882
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features-ai.ts
883
+ import fs9 from "fs";
884
+ import path9 from "path";
869
885
  function getWorkspaceAiFeatureRequiredFiles(aiFeature) {
870
886
  return Array.from(new Set([
871
887
  aiFeature.aiSchemaFile,
872
888
  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"),
889
+ path9.join(path9.dirname(aiFeature.typesFile), "api-schemas", "feature-request.schema.json"),
890
+ path9.join(path9.dirname(aiFeature.typesFile), "api-schemas", "feature-response.schema.json"),
891
+ path9.join(path9.dirname(aiFeature.typesFile), "api-schemas", "feature-result.schema.json"),
876
892
  aiFeature.clientFile,
877
893
  aiFeature.dataFile,
878
894
  aiFeature.openApiFile,
@@ -887,25 +903,40 @@ function checkWorkspaceAiFeatureConfig(aiFeature) {
887
903
  }
888
904
  function checkWorkspaceAiFeatureBootstrap(projectDir, packageName, phpPrefix) {
889
905
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
890
- if (!fs5.existsSync(bootstrapPath)) {
891
- return createDoctorCheck("AI feature bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
906
+ if (!fs9.existsSync(bootstrapPath)) {
907
+ return createDoctorCheck("AI feature bootstrap", "fail", `Missing ${path9.basename(bootstrapPath)}`);
892
908
  }
893
- const source = fs5.readFileSync(bootstrapPath, "utf8");
909
+ const source = fs9.readFileSync(bootstrapPath, "utf8");
894
910
  const registerFunctionName = `${phpPrefix}_register_ai_features`;
895
911
  const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
896
912
  const hasServerGlob = source.includes(WORKSPACE_AI_FEATURE_GLOB);
897
913
  const hasRegisterHook = source.includes(registerHook);
898
914
  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
915
  }
916
+ function getWorkspaceAiFeatureDoctorChecks(workspace, aiFeatures) {
917
+ const checks = [];
918
+ if (aiFeatures.length > 0) {
919
+ checks.push(checkWorkspaceAiFeatureBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
920
+ }
921
+ for (const aiFeature of aiFeatures) {
922
+ checks.push(checkWorkspaceAiFeatureConfig(aiFeature));
923
+ checks.push(checkExistingFiles(workspace.projectDir, `AI feature ${aiFeature.slug}`, getWorkspaceAiFeatureRequiredFiles(aiFeature)));
924
+ }
925
+ return checks;
926
+ }
927
+
928
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features-editor-plugins.ts
929
+ import fs10 from "fs";
930
+ import path10 from "path";
900
931
  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");
932
+ const editorPluginDir = path10.join("src", "editor-plugins", editorPlugin.slug);
933
+ const surfaceFile = editorPlugin.slot === "PluginSidebar" ? path10.join(editorPluginDir, "Sidebar.tsx") : path10.join(editorPluginDir, "Surface.tsx");
903
934
  return Array.from(new Set([
904
935
  editorPlugin.file,
905
936
  surfaceFile,
906
- path5.join(editorPluginDir, "data.ts"),
907
- path5.join(editorPluginDir, "types.ts"),
908
- path5.join(editorPluginDir, "style.scss")
937
+ path10.join(editorPluginDir, "data.ts"),
938
+ path10.join(editorPluginDir, "types.ts"),
939
+ path10.join(editorPluginDir, "style.scss")
909
940
  ]));
910
941
  }
911
942
  function checkWorkspaceEditorPluginConfig(editorPlugin) {
@@ -915,10 +946,10 @@ function checkWorkspaceEditorPluginConfig(editorPlugin) {
915
946
  }
916
947
  function checkWorkspaceEditorPluginBootstrap(projectDir, packageName, phpPrefix) {
917
948
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
918
- if (!fs5.existsSync(bootstrapPath)) {
919
- return createDoctorCheck("Editor plugin bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
949
+ if (!fs10.existsSync(bootstrapPath)) {
950
+ return createDoctorCheck("Editor plugin bootstrap", "fail", `Missing ${path10.basename(bootstrapPath)}`);
920
951
  }
921
- const source = fs5.readFileSync(bootstrapPath, "utf8");
952
+ const source = fs10.readFileSync(bootstrapPath, "utf8");
922
953
  const enqueueFunctionName = `${phpPrefix}_enqueue_editor_plugins_editor`;
923
954
  const enqueueHook = `add_action( 'enqueue_block_editor_assets', '${enqueueFunctionName}' );`;
924
955
  const hasEditorEnqueueHook = source.includes(enqueueHook);
@@ -929,151 +960,205 @@ function checkWorkspaceEditorPluginBootstrap(projectDir, packageName, phpPrefix)
929
960
  }
930
961
  function checkWorkspaceEditorPluginIndex(projectDir, editorPlugins) {
931
962
  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)));
963
+ path10.join("src", "editor-plugins", "index.ts"),
964
+ path10.join("src", "editor-plugins", "index.js")
965
+ ].find((relativePath) => fs10.existsSync(path10.join(projectDir, relativePath)));
935
966
  if (!indexRelativePath) {
936
967
  return createDoctorCheck("Editor plugins index", "fail", "Missing src/editor-plugins/index.ts or src/editor-plugins/index.js");
937
968
  }
938
- const indexPath = path5.join(projectDir, indexRelativePath);
939
- const source = fs5.readFileSync(indexPath, "utf8");
969
+ const indexPath = path10.join(projectDir, indexRelativePath);
970
+ const source = fs10.readFileSync(indexPath, "utf8");
940
971
  const missingImports = editorPlugins.filter((editorPlugin) => {
941
972
  const importPattern = new RegExp(`['"\`]\\./${escapeRegex(editorPlugin.slug)}(?:/[^'"\`]*)?['"\`]`, "u");
942
973
  return !importPattern.test(source);
943
974
  });
944
975
  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
976
  }
946
- function getWorkspaceAdminViewRequiredFiles(adminView) {
947
- const adminViewDir = path5.join("src", "admin-views", adminView.slug);
977
+ function getWorkspaceEditorPluginDoctorChecks(workspace, editorPlugins) {
978
+ const checks = [];
979
+ if (editorPlugins.length > 0) {
980
+ checks.push(checkWorkspaceEditorPluginBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
981
+ checks.push(checkWorkspaceEditorPluginIndex(workspace.projectDir, editorPlugins));
982
+ }
983
+ for (const editorPlugin of editorPlugins) {
984
+ checks.push(checkExistingFiles(workspace.projectDir, `Editor plugin ${editorPlugin.slug}`, getWorkspaceEditorPluginRequiredFiles(editorPlugin)));
985
+ checks.push(checkWorkspaceEditorPluginConfig(editorPlugin));
986
+ }
987
+ return checks;
988
+ }
989
+
990
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features-post-meta.ts
991
+ import fs11 from "fs";
992
+ import path11 from "path";
993
+ function getWorkspacePostMetaRequiredFiles(postMeta) {
948
994
  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")
995
+ postMeta.phpFile,
996
+ postMeta.schemaFile,
997
+ postMeta.typesFile
956
998
  ]));
957
999
  }
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");
1000
+ function checkWorkspacePostMetaConfig(postMeta) {
1001
+ let hasPostType = false;
1002
+ try {
1003
+ hasPostType = assertValidPostMetaPostType(postMeta.postType) === postMeta.postType;
1004
+ } catch {
1005
+ hasPostType = false;
961
1006
  }
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);
1007
+ const hasMetaKey = typeof postMeta.metaKey === "string" && postMeta.metaKey.trim().length > 0 && !/\s/u.test(postMeta.metaKey);
1008
+ const hasRestExposure = typeof postMeta.showInRest === "boolean";
1009
+ 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
1010
  }
974
- function checkWorkspaceAdminViewBootstrap(projectDir, packageName, phpPrefix) {
1011
+ function checkWorkspacePostMetaBootstrap(projectDir, packageName, phpPrefix) {
975
1012
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
976
- if (!fs5.existsSync(bootstrapPath)) {
977
- return createDoctorCheck("Admin view bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
1013
+ if (!fs11.existsSync(bootstrapPath)) {
1014
+ return createDoctorCheck("Post meta bootstrap", "fail", `Missing ${path11.basename(bootstrapPath)}`);
978
1015
  }
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");
993
- }
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(", ")}`);
1016
+ const source = fs11.readFileSync(bootstrapPath, "utf8");
1017
+ const registerFunctionName = `${phpPrefix}_register_post_meta_contracts`;
1018
+ const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
1019
+ const hasServerGlob = source.includes(WORKSPACE_POST_META_GLOB);
1020
+ const hasRegisterHook = source.includes(registerHook);
1021
+ 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
1022
  }
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}`);
1023
+ function checkWorkspacePostMetaPhp(projectDir, postMeta) {
1024
+ const phpPath = path11.join(projectDir, postMeta.phpFile);
1025
+ if (!fs11.existsSync(phpPath)) {
1026
+ return createDoctorCheck(`Post meta PHP ${postMeta.slug}`, "fail", `Missing ${postMeta.phpFile}`);
1006
1027
  }
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");
1028
+ const source = fs11.readFileSync(phpPath, "utf8");
1029
+ const hasRegisterPostMeta = source.includes("register_post_meta");
1030
+ const hasPostType = source.includes(postMeta.postType);
1031
+ const hasMetaKey = source.includes(postMeta.metaKey);
1032
+ const hasSchemaFile = source.includes(postMeta.schemaFile);
1033
+ const hasRestExposure = source.includes("'show_in_rest'");
1034
+ 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
1035
  }
1016
- function getWorkspaceFeatureDoctorChecks(workspace, inventory) {
1036
+ function getWorkspacePostMetaDoctorChecks(workspace, postMetaEntries) {
1017
1037
  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) {
1038
+ if (postMetaEntries.length > 0) {
1026
1039
  checks.push(checkWorkspacePostMetaBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1027
1040
  }
1028
- for (const postMeta of inventory.postMeta) {
1041
+ for (const postMeta of postMetaEntries) {
1029
1042
  checks.push(checkWorkspacePostMetaConfig(postMeta));
1030
1043
  checks.push(checkExistingFiles(workspace.projectDir, `Post meta ${postMeta.slug}`, getWorkspacePostMetaRequiredFiles(postMeta)));
1031
1044
  checks.push(checkWorkspacePostMetaPhp(workspace.projectDir, postMeta));
1032
1045
  }
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));
1046
+ return checks;
1047
+ }
1048
+
1049
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features-rest.ts
1050
+ import fs12 from "fs";
1051
+ import path12 from "path";
1052
+ function isManualRestResource(restResource) {
1053
+ return restResource.mode === "manual";
1054
+ }
1055
+ function getWorkspaceRestResourceRequiredFiles(restResource) {
1056
+ const schemaNames = new Set;
1057
+ if (isManualRestResource(restResource)) {
1058
+ schemaNames.add("query");
1059
+ if (restResource.bodyTypeName) {
1060
+ schemaNames.add("request");
1061
+ }
1062
+ schemaNames.add("response");
1063
+ return Array.from(new Set([
1064
+ restResource.apiFile,
1065
+ ...Array.from(schemaNames, (schemaName) => path12.join(path12.dirname(restResource.typesFile), "api-schemas", `${schemaName}.schema.json`)),
1066
+ restResource.clientFile,
1067
+ restResource.openApiFile,
1068
+ restResource.typesFile,
1069
+ restResource.validatorsFile
1070
+ ]));
1036
1071
  }
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)));
1072
+ if (restResource.methods.includes("list")) {
1073
+ schemaNames.add("list-query");
1074
+ schemaNames.add("list-response");
1040
1075
  }
1041
- if (inventory.aiFeatures.length > 0) {
1042
- checks.push(checkWorkspaceAiFeatureBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1076
+ if (restResource.methods.includes("read")) {
1077
+ schemaNames.add("read-query");
1078
+ schemaNames.add("read-response");
1043
1079
  }
1044
- for (const aiFeature of inventory.aiFeatures) {
1045
- checks.push(checkWorkspaceAiFeatureConfig(aiFeature));
1046
- checks.push(checkExistingFiles(workspace.projectDir, `AI feature ${aiFeature.slug}`, getWorkspaceAiFeatureRequiredFiles(aiFeature)));
1080
+ if (restResource.methods.includes("create")) {
1081
+ schemaNames.add("create-request");
1082
+ schemaNames.add("create-response");
1047
1083
  }
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));
1084
+ if (restResource.methods.includes("update")) {
1085
+ schemaNames.add("update-query");
1086
+ schemaNames.add("update-request");
1087
+ schemaNames.add("update-response");
1051
1088
  }
1052
- for (const editorPlugin of inventory.editorPlugins) {
1053
- checks.push(checkExistingFiles(workspace.projectDir, `Editor plugin ${editorPlugin.slug}`, getWorkspaceEditorPluginRequiredFiles(editorPlugin)));
1054
- checks.push(checkWorkspaceEditorPluginConfig(editorPlugin));
1089
+ if (restResource.methods.includes("delete")) {
1090
+ schemaNames.add("delete-query");
1091
+ schemaNames.add("delete-response");
1055
1092
  }
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));
1093
+ return Array.from(new Set([
1094
+ restResource.apiFile,
1095
+ ...Array.from(schemaNames, (schemaName) => path12.join(path12.dirname(restResource.typesFile), "api-schemas", `${schemaName}.schema.json`)),
1096
+ restResource.clientFile,
1097
+ ...restResource.dataFile ? [restResource.dataFile] : [],
1098
+ restResource.openApiFile,
1099
+ ...restResource.phpFile ? [restResource.phpFile] : [],
1100
+ restResource.typesFile,
1101
+ restResource.validatorsFile
1102
+ ]));
1103
+ }
1104
+ function checkWorkspaceRestResourceConfig(restResource) {
1105
+ const hasNamespace = REST_RESOURCE_NAMESPACE_PATTERN.test(restResource.namespace);
1106
+ if (isManualRestResource(restResource)) {
1107
+ const hasAuth = restResource.auth == null || MANUAL_REST_CONTRACT_AUTH_IDS.includes(restResource.auth);
1108
+ const hasMethod = typeof restResource.method === "string" && MANUAL_REST_CONTRACT_HTTP_METHOD_IDS.includes(restResource.method);
1109
+ const hasPathPattern = typeof restResource.pathPattern === "string" && restResource.pathPattern.startsWith("/") && restResource.pathPattern.length > 1;
1110
+ 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
1111
  }
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));
1112
+ const hasMethods = restResource.methods.length > 0 && restResource.methods.every((method) => REST_RESOURCE_METHOD_IDS.includes(method));
1113
+ const hasGeneratedFiles = typeof restResource.dataFile === "string" && restResource.dataFile.length > 0 && typeof restResource.phpFile === "string" && restResource.phpFile.length > 0;
1114
+ const hasRoutePattern = restResource.routePattern == null || typeof restResource.routePattern === "string" && restResource.routePattern.startsWith("/") && restResource.routePattern.length > 1 && !/\s/u.test(restResource.routePattern) && isGeneratedRestResourceRoutePatternCompatible(restResource.routePattern);
1115
+ 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");
1116
+ }
1117
+ function checkWorkspaceRestResourceBootstrap(projectDir, packageName, phpPrefix) {
1118
+ const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
1119
+ if (!fs12.existsSync(bootstrapPath)) {
1120
+ return createDoctorCheck("REST resource bootstrap", "fail", `Missing ${path12.basename(bootstrapPath)}`);
1121
+ }
1122
+ const source = fs12.readFileSync(bootstrapPath, "utf8");
1123
+ const registerFunctionName = `${phpPrefix}_register_rest_resources`;
1124
+ const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
1125
+ const hasServerGlob = source.includes(WORKSPACE_REST_RESOURCE_GLOB);
1126
+ const hasRegisterHook = source.includes(registerHook);
1127
+ 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");
1128
+ }
1129
+ function getWorkspaceRestResourceDoctorChecks(workspace, restResources) {
1130
+ const checks = [];
1131
+ if (restResources.some((restResource) => !isManualRestResource(restResource))) {
1132
+ checks.push(checkWorkspaceRestResourceBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1133
+ }
1134
+ for (const restResource of restResources) {
1135
+ checks.push(checkWorkspaceRestResourceConfig(restResource));
1136
+ checks.push(checkExistingFiles(workspace.projectDir, `REST resource ${restResource.slug}`, getWorkspaceRestResourceRequiredFiles(restResource)));
1064
1137
  }
1065
1138
  return checks;
1066
1139
  }
1067
1140
 
1141
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features.ts
1142
+ function getWorkspaceFeatureDoctorChecks(workspace, inventory) {
1143
+ return [
1144
+ ...getWorkspaceRestResourceDoctorChecks(workspace, inventory.restResources),
1145
+ ...getWorkspacePostMetaDoctorChecks(workspace, inventory.postMeta),
1146
+ ...getWorkspaceAbilityDoctorChecks(workspace, inventory.abilities),
1147
+ ...getWorkspaceAiFeatureDoctorChecks(workspace, inventory.aiFeatures),
1148
+ ...getWorkspaceEditorPluginDoctorChecks(workspace, inventory.editorPlugins),
1149
+ ...getWorkspaceAdminViewDoctorChecks(workspace, inventory)
1150
+ ];
1151
+ }
1152
+
1068
1153
  // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-package.ts
1069
- import path6 from "path";
1154
+ import path13 from "path";
1070
1155
  async function prepareWorkspacePackageDoctorSnapshot(workspace, packageJson) {
1071
1156
  const packageName = packageJson.name;
1072
1157
  const bootstrapRelativePath = getWorkspaceBootstrapRelativePath(typeof packageName === "string" && packageName.length > 0 ? packageName : workspace.packageName);
1073
- const migrationConfigRelativePath = path6.join("src", "migrations", "config.ts");
1158
+ const migrationConfigRelativePath = path13.join("src", "migrations", "config.ts");
1074
1159
  const [bootstrapExists, migrationConfigExists] = await Promise.all([
1075
- pathExists(path6.join(workspace.projectDir, bootstrapRelativePath)),
1076
- pathExists(path6.join(workspace.projectDir, migrationConfigRelativePath))
1160
+ pathExists(path13.join(workspace.projectDir, bootstrapRelativePath)),
1161
+ pathExists(path13.join(workspace.projectDir, migrationConfigRelativePath))
1077
1162
  ]);
1078
1163
  return {
1079
1164
  bootstrapExists,
@@ -1182,13 +1267,67 @@ async function getWorkspaceDoctorChecks(cwd) {
1182
1267
  }
1183
1268
 
1184
1269
  // ../wp-typia-project-tools/src/runtime/cli-doctor.ts
1270
+ var DEFAULT_DOCTOR_EXIT_POLICY = "strict";
1271
+ function annotateDoctorChecks(checks, scope) {
1272
+ return checks.map((check) => ({
1273
+ ...check,
1274
+ scope: check.scope ?? scope
1275
+ }));
1276
+ }
1277
+ function resolveDoctorExitPolicy(options) {
1278
+ return options.exitPolicy ?? DEFAULT_DOCTOR_EXIT_POLICY;
1279
+ }
1280
+ function doesCheckContributeToExit(check, exitPolicy) {
1281
+ if (check.status !== "fail") {
1282
+ return false;
1283
+ }
1284
+ if (exitPolicy === "strict") {
1285
+ return true;
1286
+ }
1287
+ return check.scope === "workspace";
1288
+ }
1289
+ function toFailureSummary(check, severity) {
1290
+ return {
1291
+ ...check.code ? { code: check.code } : {},
1292
+ label: check.label,
1293
+ scope: check.scope ?? "unknown",
1294
+ severity
1295
+ };
1296
+ }
1185
1297
  async function getDoctorChecks(cwd) {
1186
1298
  return [
1187
- ...await getEnvironmentDoctorChecks(cwd),
1188
- ...await getWorkspaceDoctorChecks(cwd)
1299
+ ...annotateDoctorChecks(await getEnvironmentDoctorChecks(cwd), "environment"),
1300
+ ...annotateDoctorChecks(await getWorkspaceDoctorChecks(cwd), "workspace")
1189
1301
  ];
1190
1302
  }
1303
+ function getDoctorExitFailureChecks(checks, options = {}) {
1304
+ const exitPolicy = resolveDoctorExitPolicy(options);
1305
+ return checks.filter((check) => doesCheckContributeToExit(check, exitPolicy));
1306
+ }
1307
+ function getDoctorExitFailureDetailLines(checks, options = {}) {
1308
+ return getDoctorExitFailureChecks(checks, options).map((check) => `${check.label}: ${check.detail}`);
1309
+ }
1310
+ function createDoctorRunSummary(checks, options = {}) {
1311
+ const exitPolicy = resolveDoctorExitPolicy(options);
1312
+ const failedChecks = checks.filter((check) => check.status === "fail");
1313
+ const exitFailureChecks = failedChecks.filter((check) => doesCheckContributeToExit(check, exitPolicy));
1314
+ const advisoryFailureChecks = failedChecks.filter((check) => !doesCheckContributeToExit(check, exitPolicy));
1315
+ const warnings = checks.filter((check) => check.status === "warn").length;
1316
+ return {
1317
+ advisoryFailureCount: advisoryFailureChecks.length,
1318
+ advisoryFailures: advisoryFailureChecks.map((check) => toFailureSummary(check, "advisory")),
1319
+ exitCode: exitFailureChecks.length > 0 ? 1 : 0,
1320
+ exitFailureCount: exitFailureChecks.length,
1321
+ exitFailures: exitFailureChecks.map((check) => toFailureSummary(check, "error")),
1322
+ exitPolicy,
1323
+ failed: failedChecks.length,
1324
+ passed: checks.length - failedChecks.length - warnings,
1325
+ total: checks.length,
1326
+ warnings
1327
+ };
1328
+ }
1191
1329
  async function runDoctor(cwd, options = {}) {
1330
+ const exitPolicy = resolveDoctorExitPolicy(options);
1192
1331
  const renderLine = options.renderLine ?? ((check) => console.log(formatDoctorCheckLine(check)));
1193
1332
  const renderSummaryLine = options.renderSummaryLine ?? (options.renderLine ? () => {
1194
1333
  return;
@@ -1197,8 +1336,9 @@ async function runDoctor(cwd, options = {}) {
1197
1336
  for (const check of checks) {
1198
1337
  renderLine(check);
1199
1338
  }
1200
- renderSummaryLine(formatDoctorSummaryLine(checks));
1201
- const failureDetailLines = getDoctorFailureDetailLines(checks);
1339
+ const exitFailureChecks = getDoctorExitFailureChecks(checks, { exitPolicy });
1340
+ renderSummaryLine(formatDoctorSummaryLine(checks, { exitFailureChecks }));
1341
+ const failureDetailLines = getDoctorExitFailureDetailLines(checks, { exitPolicy });
1202
1342
  if (failureDetailLines.length > 0) {
1203
1343
  throw createCliCommandError({
1204
1344
  code: CLI_DIAGNOSTIC_CODES.DOCTOR_CHECK_FAILED,
@@ -1211,7 +1351,10 @@ async function runDoctor(cwd, options = {}) {
1211
1351
  }
1212
1352
  export {
1213
1353
  runDoctor,
1214
- getDoctorChecks
1354
+ getDoctorExitFailureDetailLines,
1355
+ getDoctorExitFailureChecks,
1356
+ getDoctorChecks,
1357
+ createDoctorRunSummary
1215
1358
  };
1216
1359
 
1217
- //# debugId=A0E15D6F99F87A7464756E2164756E21
1360
+ //# debugId=3074C8F7DE85281764756E2164756E21