wp-typia 0.22.9 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,15 @@
1
1
  // @bun
2
2
  import {
3
+ hasAdminViewManualSettingsRouteParameters,
3
4
  hasExecutablePattern,
4
5
  hasUncommentedPattern,
6
+ isAdminViewManualSettingsRestResource,
5
7
  maskTypeScriptCommentsAndLiterals
6
- } from "./cli-rwjkqjhs.js";
8
+ } from "./cli-j8et6jvr.js";
7
9
  import {
8
10
  getBuiltInTemplateLayerDirs,
9
11
  isOmittableBuiltInTemplateLayerDir
10
- } from "./cli-xw1wbxf3.js";
12
+ } from "./cli-c2acv5dv.js";
11
13
  import {
12
14
  isBuiltInTemplateId,
13
15
  listTemplates
@@ -16,15 +18,20 @@ import {
16
18
  EDITOR_PLUGIN_SLOT_IDS,
17
19
  HOOKED_BLOCK_ANCHOR_PATTERN,
18
20
  HOOKED_BLOCK_POSITION_SET,
21
+ MANUAL_REST_CONTRACT_AUTH_IDS,
22
+ MANUAL_REST_CONTRACT_HTTP_METHOD_IDS,
19
23
  REST_RESOURCE_METHOD_IDS,
20
24
  REST_RESOURCE_NAMESPACE_PATTERN,
25
+ assertValidPostMetaPostType,
21
26
  escapeRegex,
22
- readWorkspaceInventory,
27
+ isGeneratedRestResourceRoutePatternCompatible,
28
+ pathExists,
29
+ readWorkspaceInventoryAsync,
23
30
  resolveEditorPluginSlotAlias
24
- } from "./cli-regw5384.js";
31
+ } from "./cli-ts9thts5.js";
25
32
  import"./cli-cvxvcw7c.js";
26
33
  import"./cli-t73q5aqz.js";
27
- import"./cli-fys8vm2t.js";
34
+ import"./cli-43mx1vfb.js";
28
35
  import {
29
36
  CLI_DIAGNOSTIC_CODES,
30
37
  createCliCommandError,
@@ -44,9 +51,55 @@ import"./cli-xnn9xjcy.js";
44
51
  // ../wp-typia-project-tools/src/runtime/cli-doctor-environment.ts
45
52
  import { execFileSync } from "child_process";
46
53
  import { access, constants as fsConstants, rm, writeFile } from "fs/promises";
47
- import fs from "fs";
54
+ import fs2 from "fs";
48
55
  import os from "os";
56
+ import path2 from "path";
57
+
58
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-shared.ts
59
+ import fs from "fs";
49
60
  import path from "path";
61
+ var WORKSPACE_BINDING_SERVER_GLOB = "/src/bindings/*/server.php";
62
+ var WORKSPACE_BINDING_EDITOR_SCRIPT = "build/bindings/index.js";
63
+ var WORKSPACE_BINDING_EDITOR_ASSET = "build/bindings/index.asset.php";
64
+ var WORKSPACE_REST_RESOURCE_GLOB = "/inc/rest/*.php";
65
+ var WORKSPACE_POST_META_GLOB = "/inc/post-meta/*.php";
66
+ var WORKSPACE_ABILITY_GLOB = "/inc/abilities/*.php";
67
+ var WORKSPACE_ABILITY_EDITOR_SCRIPT = "build/abilities/index.js";
68
+ var WORKSPACE_ABILITY_EDITOR_ASSET = "build/abilities/index.asset.php";
69
+ var WORKSPACE_AI_FEATURE_GLOB = "/inc/ai-features/*.php";
70
+ var WORKSPACE_ADMIN_VIEW_GLOB = "/inc/admin-views/*.php";
71
+ var WORKSPACE_ADMIN_VIEW_SCRIPT = "build/admin-views/index.js";
72
+ var WORKSPACE_ADMIN_VIEW_ASSET = "build/admin-views/index.asset.php";
73
+ var WORKSPACE_ADMIN_VIEW_STYLE = "build/admin-views/style-index.css";
74
+ var WORKSPACE_EDITOR_PLUGIN_EDITOR_SCRIPT = "build/editor-plugins/index.js";
75
+ var WORKSPACE_EDITOR_PLUGIN_EDITOR_ASSET = "build/editor-plugins/index.asset.php";
76
+ var WORKSPACE_EDITOR_PLUGIN_EDITOR_STYLE = "build/editor-plugins/style-index.css";
77
+ var WORKSPACE_GENERATED_BLOCK_ARTIFACTS = [
78
+ "block.json",
79
+ "typia.manifest.json",
80
+ "typia.schema.json",
81
+ "typia-validator.php",
82
+ "typia.openapi.json"
83
+ ];
84
+ var WORKSPACE_FULL_BLOCK_NAME_PATTERN = /^[a-z0-9-]+\/[a-z0-9-]+$/u;
85
+ function createDoctorCheck(label, status, detail, code) {
86
+ return code ? { code, detail, label, status } : { detail, label, status };
87
+ }
88
+ function createDoctorScopeCheck(status, detail) {
89
+ return createDoctorCheck("Doctor scope", status, detail);
90
+ }
91
+ function getWorkspaceBootstrapRelativePath(packageName) {
92
+ return `${packageName.split("/").pop() ?? packageName}.php`;
93
+ }
94
+ function resolveWorkspaceBootstrapPath(projectDir, packageName) {
95
+ return path.join(projectDir, getWorkspaceBootstrapRelativePath(packageName));
96
+ }
97
+ function checkExistingFiles(projectDir, label, filePaths) {
98
+ const missing = filePaths.filter((filePath) => typeof filePath === "string").filter((filePath) => !fs.existsSync(path.join(projectDir, filePath)));
99
+ return createDoctorCheck(label, missing.length === 0 ? "pass" : "fail", missing.length === 0 ? "All referenced files exist" : `Missing: ${missing.join(", ")}`);
100
+ }
101
+
102
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-environment.ts
50
103
  function readCommandVersion(command, args = ["--version"]) {
51
104
  try {
52
105
  return execFileSync(command, args, {
@@ -70,7 +123,7 @@ async function checkWritableDirectory(directory) {
70
123
  }
71
124
  }
72
125
  async function checkTempDirectory() {
73
- const tempFile = path.join(os.tmpdir(), `wp-typia-${Date.now()}.tmp`);
126
+ const tempFile = path2.join(os.tmpdir(), `wp-typia-${Date.now()}.tmp`);
74
127
  try {
75
128
  await writeFile(tempFile, "ok", "utf8");
76
129
  await rm(tempFile, { force: true });
@@ -79,15 +132,12 @@ async function checkTempDirectory() {
79
132
  return false;
80
133
  }
81
134
  }
82
- function createDoctorCheck(label, status, detail) {
83
- return { detail, label, status };
84
- }
85
135
  function getTemplateDoctorChecks() {
86
136
  const checks = [];
87
137
  for (const template of listTemplates()) {
88
138
  if (!isBuiltInTemplateId(template.id)) {
89
- const templateDirExists = fs.existsSync(template.templateDir);
90
- const hasAssets2 = templateDirExists && fs.existsSync(path.join(template.templateDir, "package.json.mustache"));
139
+ const templateDirExists = fs2.existsSync(template.templateDir);
140
+ const hasAssets2 = templateDirExists && fs2.existsSync(path2.join(template.templateDir, "package.json.mustache"));
91
141
  checks.push({
92
142
  status: !templateDirExists || hasAssets2 ? "pass" : "fail",
93
143
  label: `Template ${template.id}`,
@@ -110,9 +160,9 @@ function getTemplateDoctorChecks() {
110
160
  persistencePolicy: "public"
111
161
  })
112
162
  ])) : getBuiltInTemplateLayerDirs(builtInTemplateId);
113
- const missingRequiredLayer = layerDirs.some((layerDir) => !fs.existsSync(layerDir) && !isOmittableBuiltInTemplateLayerDir(builtInTemplateId, layerDir));
114
- const existingLayerDirs = layerDirs.filter((layerDir) => fs.existsSync(layerDir));
115
- const hasAssets = !missingRequiredLayer && existingLayerDirs.some((layerDir) => fs.existsSync(path.join(layerDir, "package.json.mustache"))) && existingLayerDirs.some((layerDir) => fs.existsSync(path.join(layerDir, "src")));
163
+ const missingRequiredLayer = layerDirs.some((layerDir) => !fs2.existsSync(layerDir) && !isOmittableBuiltInTemplateLayerDir(builtInTemplateId, layerDir));
164
+ const existingLayerDirs = layerDirs.filter((layerDir) => fs2.existsSync(layerDir));
165
+ const hasAssets = !missingRequiredLayer && existingLayerDirs.some((layerDir) => fs2.existsSync(path2.join(layerDir, "package.json.mustache"))) && existingLayerDirs.some((layerDir) => fs2.existsSync(path2.join(layerDir, "src")));
116
166
  checks.push({
117
167
  status: hasAssets ? "pass" : "fail",
118
168
  label: `Template ${template.id}`,
@@ -141,72 +191,27 @@ async function getEnvironmentDoctorChecks(cwd) {
141
191
  import fs3 from "fs";
142
192
  import path3 from "path";
143
193
  import { parseScaffoldBlockMetadata } from "@wp-typia/block-runtime/blocks";
144
-
145
- // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-shared.ts
146
- import fs2 from "fs";
147
- import path2 from "path";
148
- var WORKSPACE_BINDING_SERVER_GLOB = "/src/bindings/*/server.php";
149
- var WORKSPACE_BINDING_EDITOR_SCRIPT = "build/bindings/index.js";
150
- var WORKSPACE_BINDING_EDITOR_ASSET = "build/bindings/index.asset.php";
151
- var WORKSPACE_REST_RESOURCE_GLOB = "/inc/rest/*.php";
152
- var WORKSPACE_ABILITY_GLOB = "/inc/abilities/*.php";
153
- var WORKSPACE_ABILITY_EDITOR_SCRIPT = "build/abilities/index.js";
154
- var WORKSPACE_ABILITY_EDITOR_ASSET = "build/abilities/index.asset.php";
155
- var WORKSPACE_AI_FEATURE_GLOB = "/inc/ai-features/*.php";
156
- var WORKSPACE_ADMIN_VIEW_GLOB = "/inc/admin-views/*.php";
157
- var WORKSPACE_ADMIN_VIEW_SCRIPT = "build/admin-views/index.js";
158
- var WORKSPACE_ADMIN_VIEW_ASSET = "build/admin-views/index.asset.php";
159
- var WORKSPACE_ADMIN_VIEW_STYLE = "build/admin-views/style-index.css";
160
- var WORKSPACE_EDITOR_PLUGIN_EDITOR_SCRIPT = "build/editor-plugins/index.js";
161
- var WORKSPACE_EDITOR_PLUGIN_EDITOR_ASSET = "build/editor-plugins/index.asset.php";
162
- var WORKSPACE_EDITOR_PLUGIN_EDITOR_STYLE = "build/editor-plugins/style-index.css";
163
- var WORKSPACE_GENERATED_BLOCK_ARTIFACTS = [
164
- "block.json",
165
- "typia.manifest.json",
166
- "typia.schema.json",
167
- "typia-validator.php",
168
- "typia.openapi.json"
169
- ];
170
- var WORKSPACE_FULL_BLOCK_NAME_PATTERN = /^[a-z0-9-]+\/[a-z0-9-]+$/u;
171
- function createDoctorCheck2(label, status, detail, code) {
172
- return code ? { code, detail, label, status } : { detail, label, status };
173
- }
174
- function createDoctorScopeCheck(status, detail) {
175
- return createDoctorCheck2("Doctor scope", status, detail);
176
- }
177
- function getWorkspaceBootstrapRelativePath(packageName) {
178
- return `${packageName.split("/").pop() ?? packageName}.php`;
179
- }
180
- function resolveWorkspaceBootstrapPath(projectDir, packageName) {
181
- return path2.join(projectDir, getWorkspaceBootstrapRelativePath(packageName));
182
- }
183
- function checkExistingFiles(projectDir, label, filePaths) {
184
- const missing = filePaths.filter((filePath) => typeof filePath === "string").filter((filePath) => !fs2.existsSync(path2.join(projectDir, filePath)));
185
- return createDoctorCheck2(label, missing.length === 0 ? "pass" : "fail", missing.length === 0 ? "All referenced files exist" : `Missing: ${missing.join(", ")}`);
186
- }
187
-
188
- // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-bindings.ts
189
194
  function checkWorkspaceBindingBootstrap(projectDir, packageName) {
190
195
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
191
196
  if (!fs3.existsSync(bootstrapPath)) {
192
- return createDoctorCheck2("Binding bootstrap", "fail", `Missing ${path3.basename(bootstrapPath)}`);
197
+ return createDoctorCheck("Binding bootstrap", "fail", `Missing ${path3.basename(bootstrapPath)}`);
193
198
  }
194
199
  const source = fs3.readFileSync(bootstrapPath, "utf8");
195
200
  const hasServerGlob = source.includes(WORKSPACE_BINDING_SERVER_GLOB);
196
201
  const hasEditorEnqueueHook = source.includes("enqueue_block_editor_assets");
197
202
  const hasEditorScript = source.includes(WORKSPACE_BINDING_EDITOR_SCRIPT);
198
203
  const hasEditorAsset = source.includes(WORKSPACE_BINDING_EDITOR_ASSET);
199
- return createDoctorCheck2("Binding bootstrap", hasServerGlob && hasEditorEnqueueHook && hasEditorScript && hasEditorAsset ? "pass" : "fail", hasServerGlob && hasEditorEnqueueHook && hasEditorScript && hasEditorAsset ? "Binding source PHP and editor bootstrap hooks are present" : "Missing binding source PHP require glob or editor enqueue hook");
204
+ return createDoctorCheck("Binding bootstrap", hasServerGlob && hasEditorEnqueueHook && hasEditorScript && hasEditorAsset ? "pass" : "fail", hasServerGlob && hasEditorEnqueueHook && hasEditorScript && hasEditorAsset ? "Binding source PHP and editor bootstrap hooks are present" : "Missing binding source PHP require glob or editor enqueue hook");
200
205
  }
201
206
  function checkWorkspaceBindingSourcesIndex(projectDir, bindingSources) {
202
207
  const indexRelativePath = [path3.join("src", "bindings", "index.ts"), path3.join("src", "bindings", "index.js")].find((relativePath) => fs3.existsSync(path3.join(projectDir, relativePath)));
203
208
  if (!indexRelativePath) {
204
- return createDoctorCheck2("Binding sources index", "fail", "Missing src/bindings/index.ts or src/bindings/index.js");
209
+ return createDoctorCheck("Binding sources index", "fail", "Missing src/bindings/index.ts or src/bindings/index.js");
205
210
  }
206
211
  const indexPath = path3.join(projectDir, indexRelativePath);
207
212
  const source = fs3.readFileSync(indexPath, "utf8");
208
213
  const missingImports = bindingSources.filter((bindingSource) => !source.includes(`./${bindingSource.slug}/editor`));
209
- return createDoctorCheck2("Binding sources index", missingImports.length === 0 ? "pass" : "fail", missingImports.length === 0 ? "Binding source editor registrations are aggregated" : `Missing editor imports for: ${missingImports.map((entry) => entry.slug).join(", ")}`);
214
+ return createDoctorCheck("Binding sources index", missingImports.length === 0 ? "pass" : "fail", missingImports.length === 0 ? "Binding source editor registrations are aggregated" : `Missing editor imports for: ${missingImports.map((entry) => entry.slug).join(", ")}`);
210
215
  }
211
216
  function checkWorkspaceBindingTarget(projectDir, workspace, registeredBlockSlugs, bindingSource) {
212
217
  const hasBlock = bindingSource.block !== undefined;
@@ -215,10 +220,10 @@ function checkWorkspaceBindingTarget(projectDir, workspace, registeredBlockSlugs
215
220
  return;
216
221
  }
217
222
  if (!bindingSource.block || !bindingSource.attribute) {
218
- return createDoctorCheck2(`Binding target ${bindingSource.slug}`, "fail", "Binding target entries must include both block and attribute.");
223
+ return createDoctorCheck(`Binding target ${bindingSource.slug}`, "fail", "Binding target entries must include both block and attribute.");
219
224
  }
220
225
  if (!registeredBlockSlugs.has(bindingSource.block)) {
221
- return createDoctorCheck2(`Binding target ${bindingSource.slug}`, "fail", `Binding target references unknown block "${bindingSource.block}".`);
226
+ return createDoctorCheck(`Binding target ${bindingSource.slug}`, "fail", `Binding target references unknown block "${bindingSource.block}".`);
222
227
  }
223
228
  const blockJsonRelativePath = path3.join("src", "blocks", bindingSource.block, "block.json");
224
229
  const blockJsonPath = path3.join(projectDir, blockJsonRelativePath);
@@ -271,7 +276,7 @@ function checkWorkspaceBindingTarget(projectDir, workspace, registeredBlockSlugs
271
276
  } else {
272
277
  issues.push(`Missing ${bindingSource.editorFile}`);
273
278
  }
274
- return createDoctorCheck2(`Binding target ${bindingSource.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `${bindingSource.block}.${bindingSource.attribute} is declared and supported` : issues.join("; "));
279
+ return createDoctorCheck(`Binding target ${bindingSource.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `${bindingSource.block}.${bindingSource.attribute} is declared and supported` : issues.join("; "));
275
280
  }
276
281
  function getWorkspaceBindingDoctorChecks(workspace, inventory) {
277
282
  const checks = [];
@@ -484,13 +489,13 @@ function checkWorkspaceBlockMetadata(projectDir, workspace, block) {
484
489
  const blockJsonRelativePath = path4.join("src", "blocks", block.slug, "block.json");
485
490
  const blockJsonPath = path4.join(projectDir, blockJsonRelativePath);
486
491
  if (!fs4.existsSync(blockJsonPath)) {
487
- return createDoctorCheck2(`Block metadata ${block.slug}`, "fail", `Missing ${blockJsonRelativePath}`);
492
+ return createDoctorCheck(`Block metadata ${block.slug}`, "fail", `Missing ${blockJsonRelativePath}`);
488
493
  }
489
494
  let blockJson;
490
495
  try {
491
496
  blockJson = parseScaffoldBlockMetadata2(JSON.parse(fs4.readFileSync(blockJsonPath, "utf8")));
492
497
  } catch (error) {
493
- return createDoctorCheck2(`Block metadata ${block.slug}`, "fail", error instanceof Error ? error.message : String(error));
498
+ return createDoctorCheck(`Block metadata ${block.slug}`, "fail", error instanceof Error ? error.message : String(error));
494
499
  }
495
500
  const expectedName = `${workspace.workspace.namespace}/${block.slug}`;
496
501
  const issues = [];
@@ -500,46 +505,46 @@ function checkWorkspaceBlockMetadata(projectDir, workspace, block) {
500
505
  if (blockJson.textdomain !== workspace.workspace.textDomain) {
501
506
  issues.push(`block.json textdomain must equal "${workspace.workspace.textDomain}"`);
502
507
  }
503
- return createDoctorCheck2(`Block metadata ${block.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `block.json matches ${expectedName} and ${workspace.workspace.textDomain}` : issues.join("; "));
508
+ return createDoctorCheck(`Block metadata ${block.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `block.json matches ${expectedName} and ${workspace.workspace.textDomain}` : issues.join("; "));
504
509
  }
505
510
  function checkWorkspaceBlockHooks(projectDir, blockSlug) {
506
511
  const blockJsonRelativePath = path4.join("src", "blocks", blockSlug, "block.json");
507
512
  const blockJsonPath = path4.join(projectDir, blockJsonRelativePath);
508
513
  if (!fs4.existsSync(blockJsonPath)) {
509
- return createDoctorCheck2(`Block hooks ${blockSlug}`, "fail", `Missing ${blockJsonRelativePath}`);
514
+ return createDoctorCheck(`Block hooks ${blockSlug}`, "fail", `Missing ${blockJsonRelativePath}`);
510
515
  }
511
516
  let blockJson;
512
517
  try {
513
518
  blockJson = parseScaffoldBlockMetadata2(JSON.parse(fs4.readFileSync(blockJsonPath, "utf8")));
514
519
  } catch (error) {
515
- return createDoctorCheck2(`Block hooks ${blockSlug}`, "fail", error instanceof Error ? error.message : String(error));
520
+ return createDoctorCheck(`Block hooks ${blockSlug}`, "fail", error instanceof Error ? error.message : String(error));
516
521
  }
517
522
  const blockHooks = blockJson.blockHooks;
518
523
  if (blockHooks === undefined) {
519
- return createDoctorCheck2(`Block hooks ${blockSlug}`, "pass", "No blockHooks metadata configured");
524
+ return createDoctorCheck(`Block hooks ${blockSlug}`, "pass", "No blockHooks metadata configured");
520
525
  }
521
526
  if (!blockHooks || typeof blockHooks !== "object" || Array.isArray(blockHooks)) {
522
- return createDoctorCheck2(`Block hooks ${blockSlug}`, "fail", `${blockJsonRelativePath} must define blockHooks as an object when present.`);
527
+ return createDoctorCheck(`Block hooks ${blockSlug}`, "fail", `${blockJsonRelativePath} must define blockHooks as an object when present.`);
523
528
  }
524
529
  const blockName = typeof blockJson.name === "string" && blockJson.name.trim().length > 0 ? blockJson.name.trim() : null;
525
530
  const invalidEntries = Object.entries(blockHooks).filter(([anchor, position]) => blockName !== null && anchor.trim() === blockName || anchor.trim().length === 0 || anchor !== anchor.trim() || !HOOKED_BLOCK_ANCHOR_PATTERN.test(anchor) || typeof position !== "string" || !HOOKED_BLOCK_POSITION_SET.has(position));
526
- return createDoctorCheck2(`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(", ")}`);
531
+ 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(", ")}`);
527
532
  }
528
533
  function checkWorkspaceBlockCollectionImport(projectDir, blockSlug) {
529
534
  const entryRelativePath = path4.join("src", "blocks", blockSlug, "index.tsx");
530
535
  const entryPath = path4.join(projectDir, entryRelativePath);
531
536
  if (!fs4.existsSync(entryPath)) {
532
- return createDoctorCheck2(`Block collection ${blockSlug}`, "fail", `Missing ${entryRelativePath}`);
537
+ return createDoctorCheck(`Block collection ${blockSlug}`, "fail", `Missing ${entryRelativePath}`);
533
538
  }
534
539
  const source = fs4.readFileSync(entryPath, "utf8");
535
540
  const hasCollectionImport = WORKSPACE_COLLECTION_IMPORT_PATTERN.test(source);
536
- return createDoctorCheck2(`Block collection ${blockSlug}`, hasCollectionImport ? "pass" : "fail", hasCollectionImport ? "Shared block collection import is present" : `Missing a shared collection import like ${WORKSPACE_COLLECTION_IMPORT_LINE}`);
541
+ 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}`);
537
542
  }
538
543
  function checkWorkspaceBlockIframeCompatibility(projectDir, blockSlug) {
539
544
  const metadataResult = readWorkspaceBlockIframeMetadata(projectDir, blockSlug);
540
545
  if (!metadataResult.document) {
541
546
  return [
542
- createDoctorCheck2(`Block iframe/API v3 ${blockSlug}`, "warn", metadataResult.error ?? `Unable to inspect ${metadataResult.blockJsonRelativePath}`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.API_VERSION)
547
+ createDoctorCheck(`Block iframe/API v3 ${blockSlug}`, "warn", metadataResult.error ?? `Unable to inspect ${metadataResult.blockJsonRelativePath}`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.API_VERSION)
543
548
  ];
544
549
  }
545
550
  const blockJson = metadataResult.document;
@@ -555,51 +560,51 @@ function checkWorkspaceBlockIframeCompatibility(projectDir, blockSlug) {
555
560
  const blockWrapperStatus = editorWrapperSources.length === 0 || hasEditorBlockPropsUsage ? "pass" : "warn";
556
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.";
557
562
  return [
558
- createDoctorCheck2(`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),
559
- createDoctorCheck2(`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),
560
- createDoctorCheck2(`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),
561
- createDoctorCheck2(`Block iframe wrapper ${blockSlug}`, blockWrapperStatus, blockWrapperDetail, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.BLOCK_PROPS)
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)
562
567
  ];
563
568
  }
564
569
  function checkWorkspacePatternBootstrap(projectDir, packageName) {
565
570
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
566
571
  if (!fs4.existsSync(bootstrapPath)) {
567
- return createDoctorCheck2("Pattern bootstrap", "fail", `Missing ${path4.basename(bootstrapPath)}`);
572
+ return createDoctorCheck("Pattern bootstrap", "fail", `Missing ${path4.basename(bootstrapPath)}`);
568
573
  }
569
574
  const source = fs4.readFileSync(bootstrapPath, "utf8");
570
575
  const hasCategoryAnchor = source.includes("register_block_pattern_category");
571
576
  const hasPatternGlob = source.includes("/src/patterns/*.php");
572
- return createDoctorCheck2("Pattern bootstrap", hasCategoryAnchor && hasPatternGlob ? "pass" : "fail", hasCategoryAnchor && hasPatternGlob ? "Pattern category and loader hooks are present" : "Missing pattern category registration or src/patterns loader hook");
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");
573
578
  }
574
579
  function checkVariationEntrypoint(projectDir, blockSlug) {
575
580
  const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
576
581
  if (!fs4.existsSync(entryPath)) {
577
- return createDoctorCheck2(`Variation entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
582
+ return createDoctorCheck(`Variation entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
578
583
  }
579
584
  const source = fs4.readFileSync(entryPath, "utf8");
580
585
  const hasImport = hasUncommentedPattern(source, WORKSPACE_VARIATIONS_IMPORT_PATTERN);
581
586
  const hasCall = hasExecutablePattern(source, WORKSPACE_VARIATIONS_CALL_PATTERN);
582
- return createDoctorCheck2(`Variation entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Variations registration hook is present" : "Missing ./variations import or registerWorkspaceVariations() call");
587
+ return createDoctorCheck(`Variation entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Variations registration hook is present" : "Missing ./variations import or registerWorkspaceVariations() call");
583
588
  }
584
589
  function checkBlockStyleEntrypoint(projectDir, blockSlug) {
585
590
  const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
586
591
  if (!fs4.existsSync(entryPath)) {
587
- return createDoctorCheck2(`Block style entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
592
+ return createDoctorCheck(`Block style entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
588
593
  }
589
594
  const source = fs4.readFileSync(entryPath, "utf8");
590
595
  const hasImport = hasUncommentedPattern(source, WORKSPACE_BLOCK_STYLES_IMPORT_PATTERN);
591
596
  const hasCall = hasExecutablePattern(source, WORKSPACE_BLOCK_STYLES_CALL_PATTERN);
592
- return createDoctorCheck2(`Block style entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Block style registration hook is present" : "Missing ./styles import or registerWorkspaceBlockStyles() call");
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");
593
598
  }
594
599
  function checkBlockTransformEntrypoint(projectDir, blockSlug) {
595
600
  const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
596
601
  if (!fs4.existsSync(entryPath)) {
597
- return createDoctorCheck2(`Block transform entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
602
+ return createDoctorCheck(`Block transform entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
598
603
  }
599
604
  const source = fs4.readFileSync(entryPath, "utf8");
600
605
  const hasImport = hasUncommentedPattern(source, WORKSPACE_BLOCK_TRANSFORMS_IMPORT_PATTERN);
601
606
  const hasCall = hasExecutablePattern(source, WORKSPACE_BLOCK_TRANSFORMS_CALL_PATTERN);
602
- return createDoctorCheck2(`Block transform entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Block transform registration hook is present" : "Missing ./transforms import or applyWorkspaceBlockTransforms(registration.settings) call");
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");
603
608
  }
604
609
  function checkBlockTransformConfig(workspace, transform) {
605
610
  const expectedTo = `${workspace.workspace.namespace}/${transform.block}`;
@@ -610,7 +615,7 @@ function checkBlockTransformConfig(workspace, transform) {
610
615
  if (transform.to !== expectedTo) {
611
616
  issues.push(`to must equal "${expectedTo}" for workspace block "${transform.block}"`);
612
617
  }
613
- return createDoctorCheck2(`Block transform config ${transform.block}/${transform.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `${transform.from} transforms into ${transform.to}` : issues.join("; "));
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("; "));
614
619
  }
615
620
  function getWorkspaceBlockDoctorChecks(workspace, inventory) {
616
621
  const checks = [];
@@ -625,7 +630,7 @@ function getWorkspaceBlockDoctorChecks(workspace, inventory) {
625
630
  const variationTargetBlocks = new Set;
626
631
  for (const variation of inventory.variations) {
627
632
  if (!registeredBlockSlugs.has(variation.block)) {
628
- checks.push(createDoctorCheck2(`Variation ${variation.block}/${variation.slug}`, "fail", `Variation references unknown block "${variation.block}"`));
633
+ checks.push(createDoctorCheck(`Variation ${variation.block}/${variation.slug}`, "fail", `Variation references unknown block "${variation.block}"`));
629
634
  continue;
630
635
  }
631
636
  variationTargetBlocks.add(variation.block);
@@ -637,7 +642,7 @@ function getWorkspaceBlockDoctorChecks(workspace, inventory) {
637
642
  const blockStyleTargetBlocks = new Set;
638
643
  for (const blockStyle of inventory.blockStyles) {
639
644
  if (!registeredBlockSlugs.has(blockStyle.block)) {
640
- checks.push(createDoctorCheck2(`Block style ${blockStyle.block}/${blockStyle.slug}`, "fail", `Block style references unknown block "${blockStyle.block}"`));
645
+ checks.push(createDoctorCheck(`Block style ${blockStyle.block}/${blockStyle.slug}`, "fail", `Block style references unknown block "${blockStyle.block}"`));
641
646
  continue;
642
647
  }
643
648
  blockStyleTargetBlocks.add(blockStyle.block);
@@ -652,7 +657,7 @@ function getWorkspaceBlockDoctorChecks(workspace, inventory) {
652
657
  const blockTransformTargetBlocks = new Set;
653
658
  for (const blockTransform of inventory.blockTransforms) {
654
659
  if (!registeredBlockSlugs.has(blockTransform.block)) {
655
- checks.push(createDoctorCheck2(`Block transform ${blockTransform.block}/${blockTransform.slug}`, "fail", `Block transform references unknown block "${blockTransform.block}"`));
660
+ checks.push(createDoctorCheck(`Block transform ${blockTransform.block}/${blockTransform.slug}`, "fail", `Block transform references unknown block "${blockTransform.block}"`));
656
661
  continue;
657
662
  }
658
663
  blockTransformTargetBlocks.add(blockTransform.block);
@@ -678,8 +683,26 @@ function getWorkspaceBlockDoctorChecks(workspace, inventory) {
678
683
  // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features.ts
679
684
  import fs5 from "fs";
680
685
  import path5 from "path";
686
+ function isManualRestResource(restResource) {
687
+ return restResource.mode === "manual";
688
+ }
681
689
  function getWorkspaceRestResourceRequiredFiles(restResource) {
682
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
+ }
683
706
  if (restResource.methods.includes("list")) {
684
707
  schemaNames.add("list-query");
685
708
  schemaNames.add("list-response");
@@ -705,29 +728,80 @@ function getWorkspaceRestResourceRequiredFiles(restResource) {
705
728
  restResource.apiFile,
706
729
  ...Array.from(schemaNames, (schemaName) => path5.join(path5.dirname(restResource.typesFile), "api-schemas", `${schemaName}.schema.json`)),
707
730
  restResource.clientFile,
708
- restResource.dataFile,
731
+ ...restResource.dataFile ? [restResource.dataFile] : [],
709
732
  restResource.openApiFile,
710
- restResource.phpFile,
733
+ ...restResource.phpFile ? [restResource.phpFile] : [],
711
734
  restResource.typesFile,
712
735
  restResource.validatorsFile
713
736
  ]));
714
737
  }
715
738
  function checkWorkspaceRestResourceConfig(restResource) {
716
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
+ }
717
746
  const hasMethods = restResource.methods.length > 0 && restResource.methods.every((method) => REST_RESOURCE_METHOD_IDS.includes(method));
718
- return createDoctorCheck2(`REST resource config ${restResource.slug}`, hasNamespace && hasMethods ? "pass" : "fail", hasNamespace && hasMethods ? `REST resource namespace ${restResource.namespace} with methods ${restResource.methods.join(", ")}` : "REST resource namespace or methods are invalid");
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");
719
750
  }
720
751
  function checkWorkspaceRestResourceBootstrap(projectDir, packageName, phpPrefix) {
721
752
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
722
753
  if (!fs5.existsSync(bootstrapPath)) {
723
- return createDoctorCheck2("REST resource bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
754
+ return createDoctorCheck("REST resource bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
724
755
  }
725
756
  const source = fs5.readFileSync(bootstrapPath, "utf8");
726
757
  const registerFunctionName = `${phpPrefix}_register_rest_resources`;
727
758
  const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
728
759
  const hasServerGlob = source.includes(WORKSPACE_REST_RESOURCE_GLOB);
729
760
  const hasRegisterHook = source.includes(registerHook);
730
- return createDoctorCheck2("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");
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");
731
805
  }
732
806
  function getWorkspaceAbilityRequiredFiles(ability) {
733
807
  return Array.from(new Set([
@@ -743,7 +817,7 @@ function getWorkspaceAbilityRequiredFiles(ability) {
743
817
  function checkWorkspaceAbilityConfig(projectDir, ability) {
744
818
  const configPath = path5.join(projectDir, ability.configFile);
745
819
  if (!fs5.existsSync(configPath)) {
746
- return createDoctorCheck2(`Ability config ${ability.slug}`, "fail", `Missing ${ability.configFile}`);
820
+ return createDoctorCheck(`Ability config ${ability.slug}`, "fail", `Missing ${ability.configFile}`);
747
821
  }
748
822
  try {
749
823
  const config = JSON.parse(fs5.readFileSync(configPath, "utf8"));
@@ -751,15 +825,15 @@ function checkWorkspaceAbilityConfig(projectDir, ability) {
751
825
  const categorySlug = typeof config.category?.slug === "string" ? config.category.slug.trim() : "";
752
826
  const hasValidAbilityId = /^[a-z0-9-]+\/[a-z0-9-]+$/u.test(abilityId);
753
827
  const hasValidCategorySlug = /^[a-z0-9-]+$/u.test(categorySlug);
754
- return createDoctorCheck2(`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.");
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.");
755
829
  } catch (error) {
756
- return createDoctorCheck2(`Ability config ${ability.slug}`, "fail", error instanceof Error ? error.message : String(error));
830
+ return createDoctorCheck(`Ability config ${ability.slug}`, "fail", error instanceof Error ? error.message : String(error));
757
831
  }
758
832
  }
759
833
  function checkWorkspaceAbilityBootstrap(projectDir, packageName, phpPrefix) {
760
834
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
761
835
  if (!fs5.existsSync(bootstrapPath)) {
762
- return createDoctorCheck2("Ability bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
836
+ return createDoctorCheck("Ability bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
763
837
  }
764
838
  const source = fs5.readFileSync(bootstrapPath, "utf8");
765
839
  const loadFunctionName = `${phpPrefix}_load_workflow_abilities`;
@@ -774,7 +848,7 @@ function checkWorkspaceAbilityBootstrap(projectDir, packageName, phpPrefix) {
774
848
  const hasEditorScript = source.includes(WORKSPACE_ABILITY_EDITOR_SCRIPT);
775
849
  const hasEditorAsset = source.includes(WORKSPACE_ABILITY_EDITOR_ASSET);
776
850
  const hasScriptModuleEnqueue = source.includes("wp_enqueue_script_module");
777
- return createDoctorCheck2("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");
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");
778
852
  }
779
853
  function checkWorkspaceAbilityIndex(projectDir, abilities) {
780
854
  const indexRelativePath = [
@@ -782,7 +856,7 @@ function checkWorkspaceAbilityIndex(projectDir, abilities) {
782
856
  path5.join("src", "abilities", "index.js")
783
857
  ].find((relativePath) => fs5.existsSync(path5.join(projectDir, relativePath)));
784
858
  if (!indexRelativePath) {
785
- return createDoctorCheck2("Abilities index", "fail", "Missing src/abilities/index.ts or src/abilities/index.js");
859
+ return createDoctorCheck("Abilities index", "fail", "Missing src/abilities/index.ts or src/abilities/index.js");
786
860
  }
787
861
  const indexPath = path5.join(projectDir, indexRelativePath);
788
862
  const source = fs5.readFileSync(indexPath, "utf8");
@@ -790,7 +864,7 @@ function checkWorkspaceAbilityIndex(projectDir, abilities) {
790
864
  const exportPattern = new RegExp(`^\\s*export\\s+(?:\\*\\s+from|\\{[^}]+\\}\\s+from)\\s+['"\`]\\./${escapeRegex(ability.slug)}\\/client['"\`]`, "mu");
791
865
  return !exportPattern.test(source);
792
866
  });
793
- return createDoctorCheck2("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(", ")}`);
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(", ")}`);
794
868
  }
795
869
  function getWorkspaceAiFeatureRequiredFiles(aiFeature) {
796
870
  return Array.from(new Set([
@@ -809,19 +883,19 @@ function getWorkspaceAiFeatureRequiredFiles(aiFeature) {
809
883
  }
810
884
  function checkWorkspaceAiFeatureConfig(aiFeature) {
811
885
  const hasNamespace = REST_RESOURCE_NAMESPACE_PATTERN.test(aiFeature.namespace);
812
- return createDoctorCheck2(`AI feature config ${aiFeature.slug}`, hasNamespace ? "pass" : "fail", hasNamespace ? `AI feature namespace ${aiFeature.namespace} is valid` : "AI feature namespace is invalid");
886
+ return createDoctorCheck(`AI feature config ${aiFeature.slug}`, hasNamespace ? "pass" : "fail", hasNamespace ? `AI feature namespace ${aiFeature.namespace} is valid` : "AI feature namespace is invalid");
813
887
  }
814
888
  function checkWorkspaceAiFeatureBootstrap(projectDir, packageName, phpPrefix) {
815
889
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
816
890
  if (!fs5.existsSync(bootstrapPath)) {
817
- return createDoctorCheck2("AI feature bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
891
+ return createDoctorCheck("AI feature bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
818
892
  }
819
893
  const source = fs5.readFileSync(bootstrapPath, "utf8");
820
894
  const registerFunctionName = `${phpPrefix}_register_ai_features`;
821
895
  const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
822
896
  const hasServerGlob = source.includes(WORKSPACE_AI_FEATURE_GLOB);
823
897
  const hasRegisterHook = source.includes(registerHook);
824
- return createDoctorCheck2("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");
898
+ 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");
825
899
  }
826
900
  function getWorkspaceEditorPluginRequiredFiles(editorPlugin) {
827
901
  const editorPluginDir = path5.join("src", "editor-plugins", editorPlugin.slug);
@@ -837,12 +911,12 @@ function getWorkspaceEditorPluginRequiredFiles(editorPlugin) {
837
911
  function checkWorkspaceEditorPluginConfig(editorPlugin) {
838
912
  const normalizedSlot = resolveEditorPluginSlotAlias(editorPlugin.slot);
839
913
  const isValidSlot = Boolean(normalizedSlot);
840
- return createDoctorCheck2(`Editor plugin config ${editorPlugin.slug}`, isValidSlot ? "pass" : "fail", isValidSlot ? `Editor plugin slot ${editorPlugin.slot} is supported as ${normalizedSlot}` : `Unsupported editor plugin slot "${editorPlugin.slot}". Expected one of: ${EDITOR_PLUGIN_SLOT_IDS.join(", ")} or legacy aliases PluginSidebar, PluginDocumentSettingPanel.`);
914
+ return createDoctorCheck(`Editor plugin config ${editorPlugin.slug}`, isValidSlot ? "pass" : "fail", isValidSlot ? `Editor plugin slot ${editorPlugin.slot} is supported as ${normalizedSlot}` : `Unsupported editor plugin slot "${editorPlugin.slot}". Expected one of: ${EDITOR_PLUGIN_SLOT_IDS.join(", ")} or legacy aliases PluginSidebar, PluginDocumentSettingPanel.`);
841
915
  }
842
916
  function checkWorkspaceEditorPluginBootstrap(projectDir, packageName, phpPrefix) {
843
917
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
844
918
  if (!fs5.existsSync(bootstrapPath)) {
845
- return createDoctorCheck2("Editor plugin bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
919
+ return createDoctorCheck("Editor plugin bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
846
920
  }
847
921
  const source = fs5.readFileSync(bootstrapPath, "utf8");
848
922
  const enqueueFunctionName = `${phpPrefix}_enqueue_editor_plugins_editor`;
@@ -851,7 +925,7 @@ function checkWorkspaceEditorPluginBootstrap(projectDir, packageName, phpPrefix)
851
925
  const hasEditorScript = source.includes(WORKSPACE_EDITOR_PLUGIN_EDITOR_SCRIPT);
852
926
  const hasEditorAsset = source.includes(WORKSPACE_EDITOR_PLUGIN_EDITOR_ASSET);
853
927
  const hasEditorStyle = source.includes(WORKSPACE_EDITOR_PLUGIN_EDITOR_STYLE);
854
- return createDoctorCheck2("Editor plugin bootstrap", hasEditorEnqueueHook && hasEditorScript && hasEditorAsset && hasEditorStyle ? "pass" : "fail", hasEditorEnqueueHook && hasEditorScript && hasEditorAsset && hasEditorStyle ? "Editor plugin enqueue hook is present" : "Missing editor plugin enqueue hook or build/editor-plugins script/style asset references");
928
+ return createDoctorCheck("Editor plugin bootstrap", hasEditorEnqueueHook && hasEditorScript && hasEditorAsset && hasEditorStyle ? "pass" : "fail", hasEditorEnqueueHook && hasEditorScript && hasEditorAsset && hasEditorStyle ? "Editor plugin enqueue hook is present" : "Missing editor plugin enqueue hook or build/editor-plugins script/style asset references");
855
929
  }
856
930
  function checkWorkspaceEditorPluginIndex(projectDir, editorPlugins) {
857
931
  const indexRelativePath = [
@@ -859,7 +933,7 @@ function checkWorkspaceEditorPluginIndex(projectDir, editorPlugins) {
859
933
  path5.join("src", "editor-plugins", "index.js")
860
934
  ].find((relativePath) => fs5.existsSync(path5.join(projectDir, relativePath)));
861
935
  if (!indexRelativePath) {
862
- return createDoctorCheck2("Editor plugins index", "fail", "Missing src/editor-plugins/index.ts or src/editor-plugins/index.js");
936
+ return createDoctorCheck("Editor plugins index", "fail", "Missing src/editor-plugins/index.ts or src/editor-plugins/index.js");
863
937
  }
864
938
  const indexPath = path5.join(projectDir, indexRelativePath);
865
939
  const source = fs5.readFileSync(indexPath, "utf8");
@@ -867,7 +941,7 @@ function checkWorkspaceEditorPluginIndex(projectDir, editorPlugins) {
867
941
  const importPattern = new RegExp(`['"\`]\\./${escapeRegex(editorPlugin.slug)}(?:/[^'"\`]*)?['"\`]`, "u");
868
942
  return !importPattern.test(source);
869
943
  });
870
- return createDoctorCheck2("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(", ")}`);
944
+ 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(", ")}`);
871
945
  }
872
946
  function getWorkspaceAdminViewRequiredFiles(adminView) {
873
947
  const adminViewDir = path5.join("src", "admin-views", adminView.slug);
@@ -883,27 +957,31 @@ function getWorkspaceAdminViewRequiredFiles(adminView) {
883
957
  }
884
958
  function checkWorkspaceAdminViewConfig(adminView, inventory) {
885
959
  if (adminView.source === undefined) {
886
- return createDoctorCheck2(`Admin view config ${adminView.slug}`, "pass", "Admin view uses a replaceable local fetcher");
960
+ return createDoctorCheck(`Admin view config ${adminView.slug}`, "pass", "Admin view uses a replaceable local fetcher");
887
961
  }
888
962
  const source = adminView.source.trim();
889
963
  const restSourceMatch = /^rest-resource:([a-z][a-z0-9-]*)$/u.exec(source);
890
964
  const coreDataSourceMatch = /^core-data:(postType|taxonomy)\/([a-z0-9][a-z0-9_-]*)$/u.exec(source);
891
965
  const restResourceSlug = restSourceMatch?.[1];
892
966
  const restResource = restResourceSlug ? inventory.restResources.find((entry) => entry.slug === restResourceSlug) : undefined;
893
- const isValid = Boolean(restResource?.methods.includes("list")) || Boolean(coreDataSourceMatch);
894
- return createDoctorCheck2(`Admin view config ${adminView.slug}`, isValid ? "pass" : "fail", isValid ? `Admin view source ${source} is list-capable` : "Admin view source must use rest-resource:<slug> with a list-capable REST resource or core-data:<postType|taxonomy>/<name>");
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);
895
973
  }
896
974
  function checkWorkspaceAdminViewBootstrap(projectDir, packageName, phpPrefix) {
897
975
  const bootstrapPath = resolveWorkspaceBootstrapPath(projectDir, packageName);
898
976
  if (!fs5.existsSync(bootstrapPath)) {
899
- return createDoctorCheck2("Admin view bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
977
+ return createDoctorCheck("Admin view bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
900
978
  }
901
979
  const source = fs5.readFileSync(bootstrapPath, "utf8");
902
980
  const loadFunctionName = `${phpPrefix}_load_admin_views`;
903
981
  const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
904
982
  const hasLoaderHook = source.includes(loadHook);
905
983
  const hasServerGlob = source.includes(WORKSPACE_ADMIN_VIEW_GLOB);
906
- return createDoctorCheck2("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");
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");
907
985
  }
908
986
  function checkWorkspaceAdminViewIndex(projectDir, adminViews) {
909
987
  const indexRelativePath = [
@@ -911,7 +989,7 @@ function checkWorkspaceAdminViewIndex(projectDir, adminViews) {
911
989
  path5.join("src", "admin-views", "index.js")
912
990
  ].find((relativePath) => fs5.existsSync(path5.join(projectDir, relativePath)));
913
991
  if (!indexRelativePath) {
914
- return createDoctorCheck2("Admin views index", "fail", "Missing src/admin-views/index.ts or src/admin-views/index.js");
992
+ return createDoctorCheck("Admin views index", "fail", "Missing src/admin-views/index.ts or src/admin-views/index.js");
915
993
  }
916
994
  const indexPath = path5.join(projectDir, indexRelativePath);
917
995
  const source = fs5.readFileSync(indexPath, "utf8");
@@ -919,12 +997,12 @@ function checkWorkspaceAdminViewIndex(projectDir, adminViews) {
919
997
  const importPattern = new RegExp(`['"\`]\\./${escapeRegex(adminView.slug)}(?:/[^'"\`]*)?['"\`]`, "u");
920
998
  return !importPattern.test(source);
921
999
  });
922
- return createDoctorCheck2("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(", ")}`);
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(", ")}`);
923
1001
  }
924
1002
  function checkWorkspaceAdminViewPhp(projectDir, adminView) {
925
1003
  const phpPath = path5.join(projectDir, adminView.phpFile);
926
1004
  if (!fs5.existsSync(phpPath)) {
927
- return createDoctorCheck2(`Admin view PHP ${adminView.slug}`, "fail", `Missing ${adminView.phpFile}`);
1005
+ return createDoctorCheck(`Admin view PHP ${adminView.slug}`, "fail", `Missing ${adminView.phpFile}`);
928
1006
  }
929
1007
  const source = fs5.readFileSync(phpPath, "utf8");
930
1008
  const hasAdminMenu = source.includes("add_submenu_page");
@@ -933,17 +1011,25 @@ function checkWorkspaceAdminViewPhp(projectDir, adminView) {
933
1011
  const hasAsset = source.includes(WORKSPACE_ADMIN_VIEW_ASSET);
934
1012
  const hasStyle = source.includes(WORKSPACE_ADMIN_VIEW_STYLE);
935
1013
  const hasComponentsStyleDependency = source.includes("'wp-components'");
936
- return createDoctorCheck2(`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");
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");
937
1015
  }
938
1016
  function getWorkspaceFeatureDoctorChecks(workspace, inventory) {
939
1017
  const checks = [];
940
- if (inventory.restResources.length > 0) {
1018
+ if (inventory.restResources.some((restResource) => !isManualRestResource(restResource))) {
941
1019
  checks.push(checkWorkspaceRestResourceBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
942
1020
  }
943
1021
  for (const restResource of inventory.restResources) {
944
1022
  checks.push(checkWorkspaceRestResourceConfig(restResource));
945
1023
  checks.push(checkExistingFiles(workspace.projectDir, `REST resource ${restResource.slug}`, getWorkspaceRestResourceRequiredFiles(restResource)));
946
1024
  }
1025
+ if (inventory.postMeta.length > 0) {
1026
+ checks.push(checkWorkspacePostMetaBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1027
+ }
1028
+ for (const postMeta of inventory.postMeta) {
1029
+ checks.push(checkWorkspacePostMetaConfig(postMeta));
1030
+ checks.push(checkExistingFiles(workspace.projectDir, `Post meta ${postMeta.slug}`, getWorkspacePostMetaRequiredFiles(postMeta)));
1031
+ checks.push(checkWorkspacePostMetaPhp(workspace.projectDir, postMeta));
1032
+ }
947
1033
  if (inventory.abilities.length > 0) {
948
1034
  checks.push(checkWorkspaceAbilityBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
949
1035
  checks.push(checkWorkspaceAbilityIndex(workspace.projectDir, inventory.abilities));
@@ -980,12 +1066,25 @@ function getWorkspaceFeatureDoctorChecks(workspace, inventory) {
980
1066
  }
981
1067
 
982
1068
  // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-package.ts
983
- import fs6 from "fs";
984
1069
  import path6 from "path";
985
- function getWorkspacePackageMetadataCheck(workspace, packageJson) {
986
- const issues = [];
1070
+ async function prepareWorkspacePackageDoctorSnapshot(workspace, packageJson) {
987
1071
  const packageName = packageJson.name;
988
1072
  const bootstrapRelativePath = getWorkspaceBootstrapRelativePath(typeof packageName === "string" && packageName.length > 0 ? packageName : workspace.packageName);
1073
+ const migrationConfigRelativePath = path6.join("src", "migrations", "config.ts");
1074
+ const [bootstrapExists, migrationConfigExists] = await Promise.all([
1075
+ pathExists(path6.join(workspace.projectDir, bootstrapRelativePath)),
1076
+ pathExists(path6.join(workspace.projectDir, migrationConfigRelativePath))
1077
+ ]);
1078
+ return {
1079
+ bootstrapExists,
1080
+ bootstrapRelativePath,
1081
+ migrationConfigExists,
1082
+ migrationConfigRelativePath
1083
+ };
1084
+ }
1085
+ function getWorkspacePackageMetadataCheck(workspace, packageJson, snapshot) {
1086
+ const issues = [];
1087
+ const packageName = packageJson.name;
989
1088
  const wpTypia = packageJson.wpTypia;
990
1089
  if (typeof packageName !== "string" || packageName.length === 0) {
991
1090
  issues.push("package.json must define a string name for workspace bootstrap resolution");
@@ -1005,19 +1104,17 @@ function getWorkspacePackageMetadataCheck(workspace, packageJson) {
1005
1104
  if (wpTypia?.phpPrefix !== workspace.workspace.phpPrefix) {
1006
1105
  issues.push(`wpTypia.phpPrefix must equal "${workspace.workspace.phpPrefix}"`);
1007
1106
  }
1008
- if (!fs6.existsSync(path6.join(workspace.projectDir, bootstrapRelativePath))) {
1009
- issues.push(`Missing bootstrap file ${bootstrapRelativePath}`);
1107
+ if (!snapshot.bootstrapExists) {
1108
+ issues.push(`Missing bootstrap file ${snapshot.bootstrapRelativePath}`);
1010
1109
  }
1011
- return createDoctorCheck2("Workspace package metadata", issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `package.json metadata aligns with ${workspace.packageName} and ${bootstrapRelativePath}` : issues.join("; "));
1110
+ return createDoctorCheck("Workspace package metadata", issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `package.json metadata aligns with ${workspace.packageName} and ${snapshot.bootstrapRelativePath}` : issues.join("; "));
1012
1111
  }
1013
- function getMigrationWorkspaceHintCheck(workspace, packageJson) {
1112
+ function getMigrationWorkspaceHintCheck(packageJson, snapshot) {
1014
1113
  const hasMigrationScript = typeof packageJson.scripts?.["migration:doctor"] === "string";
1015
- const migrationConfigRelativePath = path6.join("src", "migrations", "config.ts");
1016
- const hasMigrationConfig = fs6.existsSync(path6.join(workspace.projectDir, migrationConfigRelativePath));
1017
- if (!hasMigrationScript && !hasMigrationConfig) {
1114
+ if (!hasMigrationScript && !snapshot.migrationConfigExists) {
1018
1115
  return null;
1019
1116
  }
1020
- return createDoctorCheck2("Migration workspace", hasMigrationConfig ? "pass" : "fail", hasMigrationConfig ? "Run `wp-typia migrate doctor --all` for migration target, snapshot, fixture, and generated artifact checks" : `Missing ${migrationConfigRelativePath} for the configured migration workspace`);
1117
+ return createDoctorCheck("Migration workspace", snapshot.migrationConfigExists ? "pass" : "fail", snapshot.migrationConfigExists ? "Run `wp-typia migrate doctor --all` for migration target, snapshot, fixture, and generated artifact checks" : `Missing ${snapshot.migrationConfigRelativePath} for the configured migration workspace`);
1021
1118
  }
1022
1119
 
1023
1120
  // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace.ts
@@ -1030,13 +1127,14 @@ function formatWorkspaceInventorySummary(inventory) {
1030
1127
  `${inventory.patterns.length} pattern(s)`,
1031
1128
  `${inventory.bindingSources.length} binding source(s)`,
1032
1129
  `${inventory.restResources.length} REST resource(s)`,
1130
+ `${inventory.postMeta.length} post meta contract(s)`,
1033
1131
  `${inventory.abilities.length} ability scaffold(s)`,
1034
1132
  `${inventory.aiFeatures.length} AI feature(s)`,
1035
1133
  `${inventory.editorPlugins.length} editor plugin(s)`,
1036
1134
  `${inventory.adminViews.length} admin view(s)`
1037
1135
  ].join(", ");
1038
1136
  }
1039
- function getWorkspaceDoctorChecks(cwd) {
1137
+ async function getWorkspaceDoctorChecks(cwd) {
1040
1138
  const checks = [];
1041
1139
  let workspace = null;
1042
1140
  let invalidWorkspaceReason = null;
@@ -1045,13 +1143,13 @@ function getWorkspaceDoctorChecks(cwd) {
1045
1143
  workspace = tryResolveWorkspaceProject(cwd);
1046
1144
  } catch (error) {
1047
1145
  checks.push(createDoctorScopeCheck("fail", "Scope: blocked before workspace checks. Environment checks ran, but workspace discovery could not continue. Fix the nearby workspace package metadata and rerun `wp-typia doctor`."));
1048
- checks.push(createDoctorCheck2("Workspace package metadata", "fail", error instanceof Error ? error.message : String(error)));
1146
+ checks.push(createDoctorCheck("Workspace package metadata", "fail", error instanceof Error ? error.message : String(error)));
1049
1147
  return checks;
1050
1148
  }
1051
1149
  if (!workspace) {
1052
1150
  if (invalidWorkspaceReason) {
1053
1151
  checks.push(createDoctorScopeCheck("fail", "Scope: blocked before workspace checks. Environment checks ran, but workspace diagnostics could not continue because a nearby wp-typia workspace candidate is invalid. Fix the workspace package metadata and rerun `wp-typia doctor`."));
1054
- checks.push(createDoctorCheck2("Workspace package metadata", "fail", invalidWorkspaceReason));
1152
+ checks.push(createDoctorCheck("Workspace package metadata", "fail", invalidWorkspaceReason));
1055
1153
  } else {
1056
1154
  checks.push(createDoctorScopeCheck("pass", "Scope: environment-only. No official wp-typia workspace root was detected, so this run only covered environment readiness. Re-run `wp-typia doctor` from a workspace root if you expected package metadata, inventory, or generated artifact checks."));
1057
1155
  }
@@ -1062,22 +1160,23 @@ function getWorkspaceDoctorChecks(cwd) {
1062
1160
  try {
1063
1161
  workspacePackageJson = parseWorkspacePackageJson(workspace.projectDir);
1064
1162
  } catch (error) {
1065
- checks.push(createDoctorCheck2("Workspace package metadata", "fail", error instanceof Error ? error.message : String(error)));
1163
+ checks.push(createDoctorCheck("Workspace package metadata", "fail", error instanceof Error ? error.message : String(error)));
1066
1164
  return checks;
1067
1165
  }
1068
- checks.push(getWorkspacePackageMetadataCheck(workspace, workspacePackageJson));
1166
+ const packageDoctorSnapshot = await prepareWorkspacePackageDoctorSnapshot(workspace, workspacePackageJson);
1167
+ checks.push(getWorkspacePackageMetadataCheck(workspace, workspacePackageJson, packageDoctorSnapshot));
1069
1168
  try {
1070
- const inventory = readWorkspaceInventory(workspace.projectDir);
1071
- checks.push(createDoctorCheck2("Workspace inventory", "pass", formatWorkspaceInventorySummary(inventory)));
1169
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
1170
+ checks.push(createDoctorCheck("Workspace inventory", "pass", formatWorkspaceInventorySummary(inventory)));
1072
1171
  checks.push(...getWorkspaceBlockDoctorChecks(workspace, inventory));
1073
1172
  checks.push(...getWorkspaceBindingDoctorChecks(workspace, inventory));
1074
1173
  checks.push(...getWorkspaceFeatureDoctorChecks(workspace, inventory));
1075
- const migrationWorkspaceCheck = getMigrationWorkspaceHintCheck(workspace, workspacePackageJson);
1174
+ const migrationWorkspaceCheck = getMigrationWorkspaceHintCheck(workspacePackageJson, packageDoctorSnapshot);
1076
1175
  if (migrationWorkspaceCheck) {
1077
1176
  checks.push(migrationWorkspaceCheck);
1078
1177
  }
1079
1178
  } catch (error) {
1080
- checks.push(createDoctorCheck2("Workspace inventory", "fail", error instanceof Error ? error.message : String(error)));
1179
+ checks.push(createDoctorCheck("Workspace inventory", "fail", error instanceof Error ? error.message : String(error)));
1081
1180
  }
1082
1181
  return checks;
1083
1182
  }
@@ -1086,7 +1185,7 @@ function getWorkspaceDoctorChecks(cwd) {
1086
1185
  async function getDoctorChecks(cwd) {
1087
1186
  return [
1088
1187
  ...await getEnvironmentDoctorChecks(cwd),
1089
- ...getWorkspaceDoctorChecks(cwd)
1188
+ ...await getWorkspaceDoctorChecks(cwd)
1090
1189
  ];
1091
1190
  }
1092
1191
  async function runDoctor(cwd, options = {}) {
@@ -1115,4 +1214,4 @@ export {
1115
1214
  getDoctorChecks
1116
1215
  };
1117
1216
 
1118
- //# debugId=C5B6D0A40DB5B07664756E2164756E21
1217
+ //# debugId=A0E15D6F99F87A7464756E2164756E21