wp-typia 0.22.2 → 0.22.3

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.
@@ -16,7 +16,7 @@ import {
16
16
  escapeRegex,
17
17
  readWorkspaceInventory,
18
18
  resolveEditorPluginSlotAlias
19
- } from "./cli-smzkbfna.js";
19
+ } from "./cli-x0h03qqe.js";
20
20
  import"./cli-t73q5aqz.js";
21
21
  import {
22
22
  CLI_DIAGNOSTIC_CODES,
@@ -129,12 +129,14 @@ async function getEnvironmentDoctorChecks(cwd) {
129
129
  ];
130
130
  }
131
131
 
132
- // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace.ts
132
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-bindings.ts
133
+ import fs3 from "fs";
134
+ import path3 from "path";
135
+ import { parseScaffoldBlockMetadata } from "@wp-typia/block-runtime/blocks";
136
+
137
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-shared.ts
133
138
  import fs2 from "fs";
134
139
  import path2 from "path";
135
- import { parseScaffoldBlockMetadata } from "@wp-typia/block-runtime/blocks";
136
- var WORKSPACE_COLLECTION_IMPORT_LINE = "import '../../collection';";
137
- var WORKSPACE_COLLECTION_IMPORT_PATTERN = /^\s*import\s+["']\.\.\/\.\.\/collection["']\s*;?\s*$/m;
138
140
  var WORKSPACE_BINDING_SERVER_GLOB = "/src/bindings/*/server.php";
139
141
  var WORKSPACE_BINDING_EDITOR_SCRIPT = "build/bindings/index.js";
140
142
  var WORKSPACE_BINDING_EDITOR_ASSET = "build/bindings/index.asset.php";
@@ -158,6 +160,135 @@ var WORKSPACE_GENERATED_BLOCK_ARTIFACTS = [
158
160
  "typia.openapi.json"
159
161
  ];
160
162
  var WORKSPACE_FULL_BLOCK_NAME_PATTERN = /^[a-z0-9-]+\/[a-z0-9-]+$/u;
163
+ function createDoctorCheck2(label, status, detail, code) {
164
+ return code ? { code, detail, label, status } : { detail, label, status };
165
+ }
166
+ function createDoctorScopeCheck(status, detail) {
167
+ return createDoctorCheck2("Doctor scope", status, detail);
168
+ }
169
+ function getWorkspaceBootstrapRelativePath(packageName) {
170
+ return `${packageName.split("/").pop() ?? packageName}.php`;
171
+ }
172
+ function checkExistingFiles(projectDir, label, filePaths) {
173
+ const missing = filePaths.filter((filePath) => typeof filePath === "string").filter((filePath) => !fs2.existsSync(path2.join(projectDir, filePath)));
174
+ return createDoctorCheck2(label, missing.length === 0 ? "pass" : "fail", missing.length === 0 ? "All referenced files exist" : `Missing: ${missing.join(", ")}`);
175
+ }
176
+
177
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-bindings.ts
178
+ function checkWorkspaceBindingBootstrap(projectDir, packageName) {
179
+ const packageBaseName = packageName.split("/").pop() ?? packageName;
180
+ const bootstrapPath = path3.join(projectDir, `${packageBaseName}.php`);
181
+ if (!fs3.existsSync(bootstrapPath)) {
182
+ return createDoctorCheck2("Binding bootstrap", "fail", `Missing ${path3.basename(bootstrapPath)}`);
183
+ }
184
+ const source = fs3.readFileSync(bootstrapPath, "utf8");
185
+ const hasServerGlob = source.includes(WORKSPACE_BINDING_SERVER_GLOB);
186
+ const hasEditorEnqueueHook = source.includes("enqueue_block_editor_assets");
187
+ const hasEditorScript = source.includes(WORKSPACE_BINDING_EDITOR_SCRIPT);
188
+ const hasEditorAsset = source.includes(WORKSPACE_BINDING_EDITOR_ASSET);
189
+ 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");
190
+ }
191
+ function checkWorkspaceBindingSourcesIndex(projectDir, bindingSources) {
192
+ const indexRelativePath = [path3.join("src", "bindings", "index.ts"), path3.join("src", "bindings", "index.js")].find((relativePath) => fs3.existsSync(path3.join(projectDir, relativePath)));
193
+ if (!indexRelativePath) {
194
+ return createDoctorCheck2("Binding sources index", "fail", "Missing src/bindings/index.ts or src/bindings/index.js");
195
+ }
196
+ const indexPath = path3.join(projectDir, indexRelativePath);
197
+ const source = fs3.readFileSync(indexPath, "utf8");
198
+ const missingImports = bindingSources.filter((bindingSource) => !source.includes(`./${bindingSource.slug}/editor`));
199
+ 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(", ")}`);
200
+ }
201
+ function checkWorkspaceBindingTarget(projectDir, workspace, registeredBlockSlugs, bindingSource) {
202
+ const hasBlock = bindingSource.block !== undefined;
203
+ const hasAttribute = bindingSource.attribute !== undefined;
204
+ if (!hasBlock && !hasAttribute) {
205
+ return;
206
+ }
207
+ if (!bindingSource.block || !bindingSource.attribute) {
208
+ return createDoctorCheck2(`Binding target ${bindingSource.slug}`, "fail", "Binding target entries must include both block and attribute.");
209
+ }
210
+ if (!registeredBlockSlugs.has(bindingSource.block)) {
211
+ return createDoctorCheck2(`Binding target ${bindingSource.slug}`, "fail", `Binding target references unknown block "${bindingSource.block}".`);
212
+ }
213
+ const blockJsonRelativePath = path3.join("src", "blocks", bindingSource.block, "block.json");
214
+ const blockJsonPath = path3.join(projectDir, blockJsonRelativePath);
215
+ const issues = [];
216
+ try {
217
+ const blockJson = parseScaffoldBlockMetadata(JSON.parse(fs3.readFileSync(blockJsonPath, "utf8")));
218
+ const attributes = blockJson.attributes;
219
+ if (!attributes || typeof attributes !== "object" || Array.isArray(attributes)) {
220
+ issues.push(`${blockJsonRelativePath} must define an attributes object`);
221
+ } else {
222
+ const attributeConfig = attributes[bindingSource.attribute];
223
+ if (!attributeConfig || typeof attributeConfig !== "object" || Array.isArray(attributeConfig)) {
224
+ issues.push(`${blockJsonRelativePath} must declare attribute "${bindingSource.attribute}"`);
225
+ }
226
+ }
227
+ } catch (error) {
228
+ issues.push(error instanceof Error ? `Unable to read ${blockJsonRelativePath}: ${error.message}` : `Unable to read ${blockJsonRelativePath}.`);
229
+ }
230
+ const serverPath = path3.join(projectDir, bindingSource.serverFile);
231
+ if (fs3.existsSync(serverPath)) {
232
+ const serverSource = fs3.readFileSync(serverPath, "utf8");
233
+ const supportedAttributesFilter = `block_bindings_supported_attributes_${workspace.workspace.namespace}/${bindingSource.block}`;
234
+ if (!serverSource.includes(supportedAttributesFilter)) {
235
+ issues.push(`${bindingSource.serverFile} must register ${supportedAttributesFilter}`);
236
+ }
237
+ if (!new RegExp(`(['"])${escapeRegex(bindingSource.attribute)}\\1`, "u").test(serverSource)) {
238
+ issues.push(`${bindingSource.serverFile} must expose attribute "${bindingSource.attribute}"`);
239
+ }
240
+ } else {
241
+ issues.push(`Missing ${bindingSource.serverFile}`);
242
+ }
243
+ const editorPath = path3.join(projectDir, bindingSource.editorFile);
244
+ if (fs3.existsSync(editorPath)) {
245
+ const editorSource = fs3.readFileSync(editorPath, "utf8");
246
+ const blockName = `${workspace.workspace.namespace}/${bindingSource.block}`;
247
+ const bindingSourceTargetMatch = editorSource.match(/export\s+const\s+BINDING_SOURCE_TARGET\s*=\s*\{([\s\S]*?)\}\s+as\s+const\s*;/u);
248
+ if (!bindingSourceTargetMatch) {
249
+ issues.push(`${bindingSource.editorFile} must export BINDING_SOURCE_TARGET`);
250
+ } else {
251
+ const targetSource = bindingSourceTargetMatch[1] ?? "";
252
+ const attributePattern = new RegExp(`\\battribute\\s*:\\s*["']${escapeRegex(bindingSource.attribute)}["']`, "u");
253
+ const blockPattern = new RegExp(`\\bblock\\s*:\\s*["']${escapeRegex(blockName)}["']`, "u");
254
+ if (!attributePattern.test(targetSource)) {
255
+ issues.push(`${bindingSource.editorFile} must document target attribute "${bindingSource.attribute}"`);
256
+ }
257
+ if (!blockPattern.test(targetSource)) {
258
+ issues.push(`${bindingSource.editorFile} must document target block "${blockName}"`);
259
+ }
260
+ }
261
+ } else {
262
+ issues.push(`Missing ${bindingSource.editorFile}`);
263
+ }
264
+ return createDoctorCheck2(`Binding target ${bindingSource.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `${bindingSource.block}.${bindingSource.attribute} is declared and supported` : issues.join("; "));
265
+ }
266
+ function getWorkspaceBindingDoctorChecks(workspace, inventory) {
267
+ const checks = [];
268
+ if (inventory.bindingSources.length > 0) {
269
+ checks.push(checkWorkspaceBindingBootstrap(workspace.projectDir, workspace.packageName));
270
+ checks.push(checkWorkspaceBindingSourcesIndex(workspace.projectDir, inventory.bindingSources));
271
+ }
272
+ const registeredBlockSlugs = new Set(inventory.blocks.map((block) => block.slug));
273
+ for (const bindingSource of inventory.bindingSources) {
274
+ checks.push(checkExistingFiles(workspace.projectDir, `Binding source ${bindingSource.slug}`, [
275
+ bindingSource.serverFile,
276
+ bindingSource.editorFile
277
+ ]));
278
+ const bindingTargetCheck = checkWorkspaceBindingTarget(workspace.projectDir, workspace, registeredBlockSlugs, bindingSource);
279
+ if (bindingTargetCheck) {
280
+ checks.push(bindingTargetCheck);
281
+ }
282
+ }
283
+ return checks;
284
+ }
285
+
286
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-blocks.ts
287
+ import fs4 from "fs";
288
+ import path4 from "path";
289
+ import { parseScaffoldBlockMetadata as parseScaffoldBlockMetadata2 } from "@wp-typia/block-runtime/blocks";
290
+ var WORKSPACE_COLLECTION_IMPORT_LINE = "import '../../collection';";
291
+ var WORKSPACE_COLLECTION_IMPORT_PATTERN = /^\s*import\s+["']\.\.\/\.\.\/collection["']\s*;?\s*$/m;
161
292
  var WORKSPACE_VARIATIONS_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceVariations\s*\}\s*from\s*["']\.\/variations["']\s*;?\s*$/mu;
162
293
  var WORKSPACE_VARIATIONS_CALL_PATTERN = /registerWorkspaceVariations\s*\(\s*\)\s*;?/u;
163
294
  var WORKSPACE_BLOCK_STYLES_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceBlockStyles\s*\}\s*from\s*["']\.\/styles["']\s*;?\s*$/mu;
@@ -193,12 +324,6 @@ var WORKSPACE_BLOCK_LOCAL_STYLE_FILES = [
193
324
  ];
194
325
  var WORKSPACE_BLOCK_IFRAME_GLOBAL_DOM_PATTERN = /\b(?:document|window)\b|\b(?:parent|top)\b(?!\s*:)/gu;
195
326
  var WORKSPACE_BLOCK_PROPS_PATTERN = /\buse(?:Block|InnerBlocks)Props(?:\.save)?\s*\(/u;
196
- function createDoctorCheck2(label, status, detail, code) {
197
- return code ? { code, detail, label, status } : { detail, label, status };
198
- }
199
- function createDoctorScopeCheck(status, detail) {
200
- return createDoctorCheck2("Doctor scope", status, detail);
201
- }
202
327
  function maskSourceSegment(segment) {
203
328
  return segment.replace(/[^\n\r]/gu, " ");
204
329
  }
@@ -261,7 +386,7 @@ function hasExecutablePattern(source, pattern) {
261
386
  return pattern.test(maskTypeScriptCommentsAndLiterals(source));
262
387
  }
263
388
  function normalizePathSeparators(relativePath) {
264
- return relativePath.split(path2.sep).join("/");
389
+ return relativePath.split(path4.sep).join("/");
265
390
  }
266
391
  function hasRegisteredBlockAsset(value) {
267
392
  if (typeof value === "string") {
@@ -273,25 +398,19 @@ function hasRegisteredBlockAsset(value) {
273
398
  return false;
274
399
  }
275
400
  function readWorkspaceBlockIframeMetadata(projectDir, blockSlug) {
276
- const blockJsonRelativePath = path2.join("src", "blocks", blockSlug, "block.json");
277
- const blockJsonPath = path2.join(projectDir, blockJsonRelativePath);
278
- if (!fs2.existsSync(blockJsonPath)) {
401
+ const blockJsonRelativePath = path4.join("src", "blocks", blockSlug, "block.json");
402
+ const blockJsonPath = path4.join(projectDir, blockJsonRelativePath);
403
+ if (!fs4.existsSync(blockJsonPath)) {
279
404
  return {
280
405
  blockJsonRelativePath,
281
406
  error: `Missing ${blockJsonRelativePath}`
282
407
  };
283
408
  }
284
409
  try {
285
- const parsed = JSON.parse(fs2.readFileSync(blockJsonPath, "utf8"));
286
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
287
- return {
288
- blockJsonRelativePath,
289
- error: `${blockJsonRelativePath} must contain a JSON object`
290
- };
291
- }
410
+ const document = parseScaffoldBlockMetadata2(JSON.parse(fs4.readFileSync(blockJsonPath, "utf8")));
292
411
  return {
293
412
  blockJsonRelativePath,
294
- document: parsed
413
+ document
295
414
  };
296
415
  } catch (error) {
297
416
  return {
@@ -301,156 +420,126 @@ function readWorkspaceBlockIframeMetadata(projectDir, blockSlug) {
301
420
  }
302
421
  }
303
422
  function isWorkspaceBlockEditorSource(relativePath) {
304
- const normalizedPath = normalizePathSeparators(relativePath);
305
- const normalizedLowerPath = normalizedPath.toLowerCase();
306
- if (!WORKSPACE_BLOCK_EDITOR_SOURCE_FILE_PATTERN.test(normalizedLowerPath) || /\.d\.[cm]?[jt]s$/u.test(normalizedLowerPath)) {
423
+ if (!WORKSPACE_BLOCK_EDITOR_SOURCE_FILE_PATTERN.test(relativePath)) {
307
424
  return false;
308
425
  }
309
- const segments = normalizedLowerPath.split("/");
310
- const fileName = segments[segments.length - 1] ?? "";
311
- const baseName = fileName.replace(/\.[^.]+$/u, "");
312
- if (WORKSPACE_BLOCK_EDITOR_SOURCE_BASENAMES.has(baseName)) {
426
+ const normalizedRelativePath = normalizePathSeparators(relativePath);
427
+ const normalizedDirName = path4.posix.dirname(normalizedRelativePath);
428
+ const normalizedBaseName = path4.posix.basename(normalizedRelativePath, path4.posix.extname(normalizedRelativePath));
429
+ if (WORKSPACE_BLOCK_EDITOR_SOURCE_BASENAMES.has(normalizedBaseName)) {
313
430
  return true;
314
431
  }
315
- return segments.slice(0, -1).some((segment) => WORKSPACE_BLOCK_EDITOR_SOURCE_DIRECTORIES.has(segment));
432
+ const pathSegments = normalizedDirName.split("/");
433
+ return pathSegments.some((segment) => WORKSPACE_BLOCK_EDITOR_SOURCE_DIRECTORIES.has(segment));
316
434
  }
317
435
  function isWorkspaceBlockSaveSource(relativePath) {
318
- const fileName = normalizePathSeparators(relativePath).split("/").pop() ?? "";
319
- return fileName.replace(/\.[^.]+$/u, "").toLowerCase() === "save";
436
+ const normalizedBaseName = path4.basename(relativePath, path4.extname(relativePath));
437
+ return normalizedBaseName === "save";
320
438
  }
321
439
  function collectWorkspaceBlockEditorSources(projectDir, blockSlug) {
322
- const blockDir = path2.join(projectDir, "src", "blocks", blockSlug);
323
- if (!fs2.existsSync(blockDir)) {
440
+ const blockDir = path4.join(projectDir, "src", "blocks", blockSlug);
441
+ if (!fs4.existsSync(blockDir)) {
324
442
  return [];
325
443
  }
326
- const sources = [];
327
- const visitDirectory = (directory) => {
328
- for (const entry of fs2.readdirSync(directory, { withFileTypes: true })) {
329
- const entryPath = path2.join(directory, entry.name);
444
+ const collected = [];
445
+ const queue = [blockDir];
446
+ while (queue.length > 0) {
447
+ const currentDir = queue.pop();
448
+ if (!currentDir) {
449
+ continue;
450
+ }
451
+ for (const entry of fs4.readdirSync(currentDir, { withFileTypes: true })) {
452
+ const absolutePath = path4.join(currentDir, entry.name);
330
453
  if (entry.isDirectory()) {
331
- visitDirectory(entryPath);
454
+ queue.push(absolutePath);
332
455
  continue;
333
456
  }
334
457
  if (!entry.isFile()) {
335
458
  continue;
336
459
  }
337
- const relativePath = normalizePathSeparators(path2.relative(projectDir, entryPath));
338
- const blockRelativePath = path2.relative(blockDir, entryPath);
339
- if (!isWorkspaceBlockEditorSource(blockRelativePath)) {
460
+ const relativePath = path4.relative(projectDir, absolutePath);
461
+ if (!isWorkspaceBlockEditorSource(relativePath)) {
340
462
  continue;
341
463
  }
342
- sources.push({
343
- relativePath,
344
- source: fs2.readFileSync(entryPath, "utf8")
464
+ collected.push({
465
+ relativePath: normalizePathSeparators(relativePath),
466
+ source: fs4.readFileSync(absolutePath, "utf8")
345
467
  });
346
468
  }
347
- };
348
- visitDirectory(blockDir);
349
- return sources;
469
+ }
470
+ return collected.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
350
471
  }
351
472
  function getSourceLineNumber(source, index) {
352
- let lineNumber = 1;
473
+ let line = 1;
353
474
  for (let cursor = 0;cursor < index; cursor += 1) {
354
475
  if (source[cursor] === `
355
476
  `) {
356
- lineNumber += 1;
477
+ line += 1;
357
478
  }
358
479
  }
359
- return lineNumber;
480
+ return line;
360
481
  }
361
- function isGlobalDomAccessCandidate(maskedSource, index, token) {
362
- if (token === "document" || token === "window") {
363
- return true;
482
+ function isGlobalDomAccessCandidate(source, index, identifier) {
483
+ const lineStart = source.lastIndexOf(`
484
+ `, index - 1) + 1;
485
+ const lineEndCandidate = source.indexOf(`
486
+ `, index);
487
+ const lineEnd = lineEndCandidate === -1 ? source.length : lineEndCandidate;
488
+ const lineSource = source.slice(lineStart, lineEnd);
489
+ const trimmedLine = lineSource.trimStart();
490
+ if (trimmedLine.startsWith("import ")) {
491
+ return false;
364
492
  }
365
- const before = maskedSource.slice(0, index);
366
- const after = maskedSource.slice(index + token.length);
367
- const previousNonWhitespace = before.match(/\S(?=\s*$)/u)?.[0] ?? "";
368
- const nextNonWhitespace = after.match(/^\s*(\S)/u)?.[1] ?? "";
369
- if (previousNonWhitespace === "." || previousNonWhitespace === "{" || previousNonWhitespace === ",") {
493
+ if (trimmedLine.startsWith("const ") || trimmedLine.startsWith("let ") || trimmedLine.startsWith("var ")) {
370
494
  return false;
371
495
  }
372
- if (nextNonWhitespace === ":") {
496
+ if (trimmedLine.startsWith("function ") || trimmedLine.startsWith("class ")) {
373
497
  return false;
374
498
  }
375
- if (/\b(?:const|function|let|var)\s+$/u.test(before)) {
499
+ const precedingCharacter = index > 0 ? source[index - 1] : "";
500
+ if (precedingCharacter === "." || precedingCharacter === "'" || precedingCharacter === '"') {
376
501
  return false;
377
502
  }
378
- return true;
503
+ return identifier === "document" || identifier === "window" || identifier === "parent" || identifier === "top";
379
504
  }
380
- function findWorkspaceBlockGlobalDomAccesses(sources) {
381
- const findings = [];
382
- for (const { relativePath, source } of sources) {
505
+ function findWorkspaceBlockGlobalDomAccesses(editorSources) {
506
+ return editorSources.flatMap(({ relativePath, source }) => {
383
507
  const maskedSource = maskTypeScriptCommentsAndLiterals(source);
384
508
  const matches = maskedSource.matchAll(WORKSPACE_BLOCK_IFRAME_GLOBAL_DOM_PATTERN);
509
+ const findings = [];
385
510
  for (const match of matches) {
386
- const index = match.index ?? 0;
387
- const matchedToken = match[0].replace(/\W+/gu, "");
388
- if (!isGlobalDomAccessCandidate(maskedSource, index, matchedToken)) {
511
+ const identifier = match[0];
512
+ const matchIndex = match.index ?? -1;
513
+ if (matchIndex < 0) {
389
514
  continue;
390
515
  }
391
- findings.push(`${relativePath}:${getSourceLineNumber(source, index)} (${matchedToken})`);
392
- if (findings.length >= 5) {
393
- return findings;
516
+ if (!isGlobalDomAccessCandidate(source, matchIndex, identifier)) {
517
+ continue;
394
518
  }
519
+ findings.push(`${relativePath}:${getSourceLineNumber(source, matchIndex)}`);
395
520
  }
396
- }
397
- return findings;
398
- }
399
- function getWorkspaceBootstrapRelativePath(packageName) {
400
- const packageBaseName = packageName.split("/").pop() ?? packageName;
401
- return `${packageBaseName}.php`;
402
- }
403
- function checkExistingFiles(projectDir, label, filePaths) {
404
- const missing = filePaths.filter((filePath) => typeof filePath === "string").filter((filePath) => !fs2.existsSync(path2.join(projectDir, filePath)));
405
- return createDoctorCheck2(label, missing.length === 0 ? "pass" : "fail", missing.length === 0 ? "All referenced files exist" : `Missing: ${missing.join(", ")}`);
406
- }
407
- function checkWorkspacePackageMetadata(workspace, packageJson) {
408
- const issues = [];
409
- const packageName = packageJson.name;
410
- const bootstrapRelativePath = getWorkspaceBootstrapRelativePath(typeof packageName === "string" && packageName.length > 0 ? packageName : workspace.packageName);
411
- const wpTypia = packageJson.wpTypia;
412
- if (typeof packageName !== "string" || packageName.length === 0) {
413
- issues.push("package.json must define a string name for workspace bootstrap resolution");
414
- }
415
- if (wpTypia?.projectType !== "workspace") {
416
- issues.push('wpTypia.projectType must be "workspace"');
417
- }
418
- if (wpTypia?.templatePackage !== WORKSPACE_TEMPLATE_PACKAGE) {
419
- issues.push(`wpTypia.templatePackage must be "${WORKSPACE_TEMPLATE_PACKAGE}"`);
420
- }
421
- if (wpTypia?.namespace !== workspace.workspace.namespace) {
422
- issues.push(`wpTypia.namespace must equal "${workspace.workspace.namespace}"`);
423
- }
424
- if (wpTypia?.textDomain !== workspace.workspace.textDomain) {
425
- issues.push(`wpTypia.textDomain must equal "${workspace.workspace.textDomain}"`);
426
- }
427
- if (wpTypia?.phpPrefix !== workspace.workspace.phpPrefix) {
428
- issues.push(`wpTypia.phpPrefix must equal "${workspace.workspace.phpPrefix}"`);
429
- }
430
- if (!fs2.existsSync(path2.join(workspace.projectDir, bootstrapRelativePath))) {
431
- issues.push(`Missing bootstrap file ${bootstrapRelativePath}`);
432
- }
433
- return createDoctorCheck2("Workspace package metadata", issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `package.json metadata aligns with ${workspace.packageName} and ${bootstrapRelativePath}` : issues.join("; "));
521
+ return findings;
522
+ });
434
523
  }
435
524
  function getWorkspaceBlockRequiredFiles(block) {
436
- const blockDir = path2.join("src", "blocks", block.slug);
525
+ const blockDir = path4.join("src", "blocks", block.slug);
437
526
  return Array.from(new Set([
438
527
  block.typesFile,
439
528
  block.apiTypesFile,
440
529
  block.openApiFile,
441
- path2.join(blockDir, "index.tsx"),
442
- ...WORKSPACE_GENERATED_BLOCK_ARTIFACTS.map((fileName) => path2.join(blockDir, fileName))
530
+ path4.join(blockDir, "index.tsx"),
531
+ ...WORKSPACE_GENERATED_BLOCK_ARTIFACTS.map((fileName) => path4.join(blockDir, fileName))
443
532
  ].filter((filePath) => typeof filePath === "string")));
444
533
  }
445
534
  function checkWorkspaceBlockMetadata(projectDir, workspace, block) {
446
- const blockJsonRelativePath = path2.join("src", "blocks", block.slug, "block.json");
447
- const blockJsonPath = path2.join(projectDir, blockJsonRelativePath);
448
- if (!fs2.existsSync(blockJsonPath)) {
535
+ const blockJsonRelativePath = path4.join("src", "blocks", block.slug, "block.json");
536
+ const blockJsonPath = path4.join(projectDir, blockJsonRelativePath);
537
+ if (!fs4.existsSync(blockJsonPath)) {
449
538
  return createDoctorCheck2(`Block metadata ${block.slug}`, "fail", `Missing ${blockJsonRelativePath}`);
450
539
  }
451
540
  let blockJson;
452
541
  try {
453
- blockJson = parseScaffoldBlockMetadata(JSON.parse(fs2.readFileSync(blockJsonPath, "utf8")));
542
+ blockJson = parseScaffoldBlockMetadata2(JSON.parse(fs4.readFileSync(blockJsonPath, "utf8")));
454
543
  } catch (error) {
455
544
  return createDoctorCheck2(`Block metadata ${block.slug}`, "fail", error instanceof Error ? error.message : String(error));
456
545
  }
@@ -465,14 +554,14 @@ function checkWorkspaceBlockMetadata(projectDir, workspace, block) {
465
554
  return createDoctorCheck2(`Block metadata ${block.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `block.json matches ${expectedName} and ${workspace.workspace.textDomain}` : issues.join("; "));
466
555
  }
467
556
  function checkWorkspaceBlockHooks(projectDir, blockSlug) {
468
- const blockJsonRelativePath = path2.join("src", "blocks", blockSlug, "block.json");
469
- const blockJsonPath = path2.join(projectDir, blockJsonRelativePath);
470
- if (!fs2.existsSync(blockJsonPath)) {
557
+ const blockJsonRelativePath = path4.join("src", "blocks", blockSlug, "block.json");
558
+ const blockJsonPath = path4.join(projectDir, blockJsonRelativePath);
559
+ if (!fs4.existsSync(blockJsonPath)) {
471
560
  return createDoctorCheck2(`Block hooks ${blockSlug}`, "fail", `Missing ${blockJsonRelativePath}`);
472
561
  }
473
562
  let blockJson;
474
563
  try {
475
- blockJson = parseScaffoldBlockMetadata(JSON.parse(fs2.readFileSync(blockJsonPath, "utf8")));
564
+ blockJson = parseScaffoldBlockMetadata2(JSON.parse(fs4.readFileSync(blockJsonPath, "utf8")));
476
565
  } catch (error) {
477
566
  return createDoctorCheck2(`Block hooks ${blockSlug}`, "fail", error instanceof Error ? error.message : String(error));
478
567
  }
@@ -488,12 +577,12 @@ function checkWorkspaceBlockHooks(projectDir, blockSlug) {
488
577
  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(", ")}`);
489
578
  }
490
579
  function checkWorkspaceBlockCollectionImport(projectDir, blockSlug) {
491
- const entryRelativePath = path2.join("src", "blocks", blockSlug, "index.tsx");
492
- const entryPath = path2.join(projectDir, entryRelativePath);
493
- if (!fs2.existsSync(entryPath)) {
580
+ const entryRelativePath = path4.join("src", "blocks", blockSlug, "index.tsx");
581
+ const entryPath = path4.join(projectDir, entryRelativePath);
582
+ if (!fs4.existsSync(entryPath)) {
494
583
  return createDoctorCheck2(`Block collection ${blockSlug}`, "fail", `Missing ${entryRelativePath}`);
495
584
  }
496
- const source = fs2.readFileSync(entryPath, "utf8");
585
+ const source = fs4.readFileSync(entryPath, "utf8");
497
586
  const hasCollectionImport = WORKSPACE_COLLECTION_IMPORT_PATTERN.test(source);
498
587
  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}`);
499
588
  }
@@ -506,8 +595,8 @@ function checkWorkspaceBlockIframeCompatibility(projectDir, blockSlug) {
506
595
  }
507
596
  const blockJson = metadataResult.document;
508
597
  const apiVersion = typeof blockJson.apiVersion === "number" && Number.isFinite(blockJson.apiVersion) ? blockJson.apiVersion : null;
509
- const blockDir = path2.join(projectDir, "src", "blocks", blockSlug);
510
- const localStyleFiles = WORKSPACE_BLOCK_LOCAL_STYLE_FILES.filter((fileName) => fs2.existsSync(path2.join(blockDir, fileName))).map((fileName) => normalizePathSeparators(path2.join("src", "blocks", blockSlug, fileName)));
598
+ const blockDir = path4.join(projectDir, "src", "blocks", blockSlug);
599
+ const localStyleFiles = WORKSPACE_BLOCK_LOCAL_STYLE_FILES.filter((fileName) => fs4.existsSync(path4.join(blockDir, fileName))).map((fileName) => normalizePathSeparators(path4.join("src", "blocks", blockSlug, fileName)));
511
600
  const hasRegisteredEditorStyles = hasRegisteredBlockAsset(blockJson.style) || hasRegisteredBlockAsset(blockJson.editorStyle);
512
601
  const editorSources = collectWorkspaceBlockEditorSources(projectDir, blockSlug);
513
602
  const editorWrapperSources = editorSources.filter((source) => !isWorkspaceBlockSaveSource(source.relativePath));
@@ -525,103 +614,122 @@ function checkWorkspaceBlockIframeCompatibility(projectDir, blockSlug) {
525
614
  }
526
615
  function checkWorkspacePatternBootstrap(projectDir, packageName) {
527
616
  const packageBaseName = packageName.split("/").pop() ?? packageName;
528
- const bootstrapPath = path2.join(projectDir, `${packageBaseName}.php`);
529
- if (!fs2.existsSync(bootstrapPath)) {
530
- return createDoctorCheck2("Pattern bootstrap", "fail", `Missing ${path2.basename(bootstrapPath)}`);
617
+ const bootstrapPath = path4.join(projectDir, `${packageBaseName}.php`);
618
+ if (!fs4.existsSync(bootstrapPath)) {
619
+ return createDoctorCheck2("Pattern bootstrap", "fail", `Missing ${path4.basename(bootstrapPath)}`);
531
620
  }
532
- const source = fs2.readFileSync(bootstrapPath, "utf8");
621
+ const source = fs4.readFileSync(bootstrapPath, "utf8");
533
622
  const hasCategoryAnchor = source.includes("register_block_pattern_category");
534
623
  const hasPatternGlob = source.includes("/src/patterns/*.php");
535
624
  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");
536
625
  }
537
- function checkWorkspaceBindingBootstrap(projectDir, packageName) {
538
- const packageBaseName = packageName.split("/").pop() ?? packageName;
539
- const bootstrapPath = path2.join(projectDir, `${packageBaseName}.php`);
540
- if (!fs2.existsSync(bootstrapPath)) {
541
- return createDoctorCheck2("Binding bootstrap", "fail", `Missing ${path2.basename(bootstrapPath)}`);
626
+ function checkVariationEntrypoint(projectDir, blockSlug) {
627
+ const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
628
+ if (!fs4.existsSync(entryPath)) {
629
+ return createDoctorCheck2(`Variation entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
542
630
  }
543
- const source = fs2.readFileSync(bootstrapPath, "utf8");
544
- const hasServerGlob = source.includes(WORKSPACE_BINDING_SERVER_GLOB);
545
- const hasEditorEnqueueHook = source.includes("enqueue_block_editor_assets");
546
- const hasEditorScript = source.includes(WORKSPACE_BINDING_EDITOR_SCRIPT);
547
- const hasEditorAsset = source.includes(WORKSPACE_BINDING_EDITOR_ASSET);
548
- 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");
631
+ const source = fs4.readFileSync(entryPath, "utf8");
632
+ const hasImport = hasUncommentedPattern(source, WORKSPACE_VARIATIONS_IMPORT_PATTERN);
633
+ const hasCall = hasExecutablePattern(source, WORKSPACE_VARIATIONS_CALL_PATTERN);
634
+ return createDoctorCheck2(`Variation entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Variations registration hook is present" : "Missing ./variations import or registerWorkspaceVariations() call");
549
635
  }
550
- function checkWorkspaceBindingSourcesIndex(projectDir, bindingSources) {
551
- const indexRelativePath = [path2.join("src", "bindings", "index.ts"), path2.join("src", "bindings", "index.js")].find((relativePath) => fs2.existsSync(path2.join(projectDir, relativePath)));
552
- if (!indexRelativePath) {
553
- return createDoctorCheck2("Binding sources index", "fail", "Missing src/bindings/index.ts or src/bindings/index.js");
636
+ function checkBlockStyleEntrypoint(projectDir, blockSlug) {
637
+ const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
638
+ if (!fs4.existsSync(entryPath)) {
639
+ return createDoctorCheck2(`Block style entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
554
640
  }
555
- const indexPath = path2.join(projectDir, indexRelativePath);
556
- const source = fs2.readFileSync(indexPath, "utf8");
557
- const missingImports = bindingSources.filter((bindingSource) => !source.includes(`./${bindingSource.slug}/editor`));
558
- 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(", ")}`);
641
+ const source = fs4.readFileSync(entryPath, "utf8");
642
+ const hasImport = hasUncommentedPattern(source, WORKSPACE_BLOCK_STYLES_IMPORT_PATTERN);
643
+ const hasCall = hasExecutablePattern(source, WORKSPACE_BLOCK_STYLES_CALL_PATTERN);
644
+ return createDoctorCheck2(`Block style entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Block style registration hook is present" : "Missing ./styles import or registerWorkspaceBlockStyles() call");
559
645
  }
560
- function checkWorkspaceBindingTarget(projectDir, workspace, registeredBlockSlugs, bindingSource) {
561
- const hasBlock = bindingSource.block !== undefined;
562
- const hasAttribute = bindingSource.attribute !== undefined;
563
- if (!hasBlock && !hasAttribute) {
564
- return;
646
+ function checkBlockTransformEntrypoint(projectDir, blockSlug) {
647
+ const entryPath = path4.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
648
+ if (!fs4.existsSync(entryPath)) {
649
+ return createDoctorCheck2(`Block transform entrypoint ${blockSlug}`, "fail", `Missing ${path4.relative(projectDir, entryPath)}`);
565
650
  }
566
- if (!bindingSource.block || !bindingSource.attribute) {
567
- return createDoctorCheck2(`Binding target ${bindingSource.slug}`, "fail", "Binding target entries must include both block and attribute.");
651
+ const source = fs4.readFileSync(entryPath, "utf8");
652
+ const hasImport = hasUncommentedPattern(source, WORKSPACE_BLOCK_TRANSFORMS_IMPORT_PATTERN);
653
+ const hasCall = hasExecutablePattern(source, WORKSPACE_BLOCK_TRANSFORMS_CALL_PATTERN);
654
+ 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");
655
+ }
656
+ function checkBlockTransformConfig(workspace, transform) {
657
+ const expectedTo = `${workspace.workspace.namespace}/${transform.block}`;
658
+ const issues = [];
659
+ if (!WORKSPACE_FULL_BLOCK_NAME_PATTERN.test(transform.from)) {
660
+ issues.push("from must use full namespace/block format");
568
661
  }
569
- if (!registeredBlockSlugs.has(bindingSource.block)) {
570
- return createDoctorCheck2(`Binding target ${bindingSource.slug}`, "fail", `Binding target references unknown block "${bindingSource.block}".`);
662
+ if (transform.to !== expectedTo) {
663
+ issues.push(`to must equal "${expectedTo}" for workspace block "${transform.block}"`);
571
664
  }
572
- const blockJsonRelativePath = path2.join("src", "blocks", bindingSource.block, "block.json");
573
- const blockJsonPath = path2.join(projectDir, blockJsonRelativePath);
574
- const issues = [];
575
- try {
576
- const blockJson = parseScaffoldBlockMetadata(JSON.parse(fs2.readFileSync(blockJsonPath, "utf8")));
577
- const attributes = blockJson.attributes;
578
- if (!attributes || typeof attributes !== "object" || Array.isArray(attributes)) {
579
- issues.push(`${blockJsonRelativePath} must define an attributes object`);
580
- } else {
581
- const attributeConfig = attributes[bindingSource.attribute];
582
- if (!attributeConfig || typeof attributeConfig !== "object" || Array.isArray(attributeConfig)) {
583
- issues.push(`${blockJsonRelativePath} must declare attribute "${bindingSource.attribute}"`);
584
- }
665
+ 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("; "));
666
+ }
667
+ function getWorkspaceBlockDoctorChecks(workspace, inventory) {
668
+ const checks = [];
669
+ for (const block of inventory.blocks) {
670
+ checks.push(checkExistingFiles(workspace.projectDir, `Block ${block.slug}`, getWorkspaceBlockRequiredFiles(block)));
671
+ checks.push(checkWorkspaceBlockMetadata(workspace.projectDir, workspace, block));
672
+ checks.push(checkWorkspaceBlockHooks(workspace.projectDir, block.slug));
673
+ checks.push(checkWorkspaceBlockCollectionImport(workspace.projectDir, block.slug));
674
+ checks.push(...checkWorkspaceBlockIframeCompatibility(workspace.projectDir, block.slug));
675
+ }
676
+ const registeredBlockSlugs = new Set(inventory.blocks.map((block) => block.slug));
677
+ const variationTargetBlocks = new Set;
678
+ for (const variation of inventory.variations) {
679
+ if (!registeredBlockSlugs.has(variation.block)) {
680
+ checks.push(createDoctorCheck2(`Variation ${variation.block}/${variation.slug}`, "fail", `Variation references unknown block "${variation.block}"`));
681
+ continue;
585
682
  }
586
- } catch (error) {
587
- issues.push(error instanceof Error ? `Unable to read ${blockJsonRelativePath}: ${error.message}` : `Unable to read ${blockJsonRelativePath}.`);
683
+ variationTargetBlocks.add(variation.block);
684
+ checks.push(checkExistingFiles(workspace.projectDir, `Variation ${variation.block}/${variation.slug}`, [variation.file]));
588
685
  }
589
- const serverPath = path2.join(projectDir, bindingSource.serverFile);
590
- if (fs2.existsSync(serverPath)) {
591
- const serverSource = fs2.readFileSync(serverPath, "utf8");
592
- const supportedAttributesFilter = `block_bindings_supported_attributes_${workspace.workspace.namespace}/${bindingSource.block}`;
593
- if (!serverSource.includes(supportedAttributesFilter)) {
594
- issues.push(`${bindingSource.serverFile} must register ${supportedAttributesFilter}`);
686
+ for (const blockSlug of variationTargetBlocks) {
687
+ checks.push(checkVariationEntrypoint(workspace.projectDir, blockSlug));
688
+ }
689
+ const blockStyleTargetBlocks = new Set;
690
+ for (const blockStyle of inventory.blockStyles) {
691
+ if (!registeredBlockSlugs.has(blockStyle.block)) {
692
+ checks.push(createDoctorCheck2(`Block style ${blockStyle.block}/${blockStyle.slug}`, "fail", `Block style references unknown block "${blockStyle.block}"`));
693
+ continue;
595
694
  }
596
- if (!new RegExp(`'${escapeRegex(bindingSource.attribute)}'`, "u").test(serverSource)) {
597
- issues.push(`${bindingSource.serverFile} must expose attribute "${bindingSource.attribute}"`);
695
+ blockStyleTargetBlocks.add(blockStyle.block);
696
+ checks.push(checkExistingFiles(workspace.projectDir, `Block style ${blockStyle.block}/${blockStyle.slug}`, [blockStyle.file]));
697
+ }
698
+ for (const blockSlug of blockStyleTargetBlocks) {
699
+ checks.push(checkExistingFiles(workspace.projectDir, `Block style registry ${blockSlug}`, [
700
+ path4.join("src", "blocks", blockSlug, "styles", "index.ts")
701
+ ]));
702
+ checks.push(checkBlockStyleEntrypoint(workspace.projectDir, blockSlug));
703
+ }
704
+ const blockTransformTargetBlocks = new Set;
705
+ for (const blockTransform of inventory.blockTransforms) {
706
+ if (!registeredBlockSlugs.has(blockTransform.block)) {
707
+ checks.push(createDoctorCheck2(`Block transform ${blockTransform.block}/${blockTransform.slug}`, "fail", `Block transform references unknown block "${blockTransform.block}"`));
708
+ continue;
598
709
  }
599
- } else {
600
- issues.push(`Missing ${bindingSource.serverFile}`);
710
+ blockTransformTargetBlocks.add(blockTransform.block);
711
+ checks.push(checkBlockTransformConfig(workspace, blockTransform));
712
+ checks.push(checkExistingFiles(workspace.projectDir, `Block transform ${blockTransform.block}/${blockTransform.slug}`, [blockTransform.file]));
601
713
  }
602
- const editorPath = path2.join(projectDir, bindingSource.editorFile);
603
- if (fs2.existsSync(editorPath)) {
604
- const editorSource = fs2.readFileSync(editorPath, "utf8");
605
- const blockName = `${workspace.workspace.namespace}/${bindingSource.block}`;
606
- const bindingSourceTargetMatch = editorSource.match(/export\s+const\s+BINDING_SOURCE_TARGET\s*=\s*\{([\s\S]*?)\}\s+as\s+const\s*;/u);
607
- if (!bindingSourceTargetMatch) {
608
- issues.push(`${bindingSource.editorFile} must export BINDING_SOURCE_TARGET`);
609
- } else {
610
- const targetSource = bindingSourceTargetMatch[1] ?? "";
611
- const attributePattern = new RegExp(`\\battribute\\s*:\\s*["']${escapeRegex(bindingSource.attribute)}["']`, "u");
612
- const blockPattern = new RegExp(`\\bblock\\s*:\\s*["']${escapeRegex(blockName)}["']`, "u");
613
- if (!attributePattern.test(targetSource)) {
614
- issues.push(`${bindingSource.editorFile} must document target attribute "${bindingSource.attribute}"`);
615
- }
616
- if (!blockPattern.test(targetSource)) {
617
- issues.push(`${bindingSource.editorFile} must document target block "${blockName}"`);
618
- }
619
- }
620
- } else {
621
- issues.push(`Missing ${bindingSource.editorFile}`);
714
+ for (const blockSlug of blockTransformTargetBlocks) {
715
+ checks.push(checkExistingFiles(workspace.projectDir, `Block transform registry ${blockSlug}`, [
716
+ path4.join("src", "blocks", blockSlug, "transforms", "index.ts")
717
+ ]));
718
+ checks.push(checkBlockTransformEntrypoint(workspace.projectDir, blockSlug));
622
719
  }
623
- return createDoctorCheck2(`Binding target ${bindingSource.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `${bindingSource.block}.${bindingSource.attribute} is declared and supported` : issues.join("; "));
720
+ const shouldCheckPatternBootstrap = inventory.patterns.length > 0 || fs4.existsSync(path4.join(workspace.projectDir, "src", "patterns"));
721
+ if (shouldCheckPatternBootstrap) {
722
+ checks.push(checkWorkspacePatternBootstrap(workspace.projectDir, workspace.packageName));
723
+ }
724
+ for (const pattern of inventory.patterns) {
725
+ checks.push(checkExistingFiles(workspace.projectDir, `Pattern ${pattern.slug}`, [pattern.file]));
726
+ }
727
+ return checks;
624
728
  }
729
+
730
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-features.ts
731
+ import fs5 from "fs";
732
+ import path5 from "path";
625
733
  function getWorkspaceRestResourceRequiredFiles(restResource) {
626
734
  const schemaNames = new Set;
627
735
  if (restResource.methods.includes("list")) {
@@ -647,7 +755,7 @@ function getWorkspaceRestResourceRequiredFiles(restResource) {
647
755
  }
648
756
  return Array.from(new Set([
649
757
  restResource.apiFile,
650
- ...Array.from(schemaNames, (schemaName) => path2.join(path2.dirname(restResource.typesFile), "api-schemas", `${schemaName}.schema.json`)),
758
+ ...Array.from(schemaNames, (schemaName) => path5.join(path5.dirname(restResource.typesFile), "api-schemas", `${schemaName}.schema.json`)),
651
759
  restResource.clientFile,
652
760
  restResource.dataFile,
653
761
  restResource.openApiFile,
@@ -663,11 +771,11 @@ function checkWorkspaceRestResourceConfig(restResource) {
663
771
  }
664
772
  function checkWorkspaceRestResourceBootstrap(projectDir, packageName, phpPrefix) {
665
773
  const packageBaseName = packageName.split("/").pop() ?? packageName;
666
- const bootstrapPath = path2.join(projectDir, `${packageBaseName}.php`);
667
- if (!fs2.existsSync(bootstrapPath)) {
668
- return createDoctorCheck2("REST resource bootstrap", "fail", `Missing ${path2.basename(bootstrapPath)}`);
774
+ const bootstrapPath = path5.join(projectDir, `${packageBaseName}.php`);
775
+ if (!fs5.existsSync(bootstrapPath)) {
776
+ return createDoctorCheck2("REST resource bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
669
777
  }
670
- const source = fs2.readFileSync(bootstrapPath, "utf8");
778
+ const source = fs5.readFileSync(bootstrapPath, "utf8");
671
779
  const registerFunctionName = `${phpPrefix}_register_rest_resources`;
672
780
  const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
673
781
  const hasServerGlob = source.includes(WORKSPACE_REST_RESOURCE_GLOB);
@@ -686,12 +794,12 @@ function getWorkspaceAbilityRequiredFiles(ability) {
686
794
  ]));
687
795
  }
688
796
  function checkWorkspaceAbilityConfig(projectDir, ability) {
689
- const configPath = path2.join(projectDir, ability.configFile);
690
- if (!fs2.existsSync(configPath)) {
797
+ const configPath = path5.join(projectDir, ability.configFile);
798
+ if (!fs5.existsSync(configPath)) {
691
799
  return createDoctorCheck2(`Ability config ${ability.slug}`, "fail", `Missing ${ability.configFile}`);
692
800
  }
693
801
  try {
694
- const config = JSON.parse(fs2.readFileSync(configPath, "utf8"));
802
+ const config = JSON.parse(fs5.readFileSync(configPath, "utf8"));
695
803
  const abilityId = typeof config.abilityId === "string" ? config.abilityId.trim() : "";
696
804
  const categorySlug = typeof config.category?.slug === "string" ? config.category.slug.trim() : "";
697
805
  const hasValidAbilityId = /^[a-z0-9-]+\/[a-z0-9-]+$/u.test(abilityId);
@@ -703,11 +811,11 @@ function checkWorkspaceAbilityConfig(projectDir, ability) {
703
811
  }
704
812
  function checkWorkspaceAbilityBootstrap(projectDir, packageName, phpPrefix) {
705
813
  const packageBaseName = packageName.split("/").pop() ?? packageName;
706
- const bootstrapPath = path2.join(projectDir, `${packageBaseName}.php`);
707
- if (!fs2.existsSync(bootstrapPath)) {
708
- return createDoctorCheck2("Ability bootstrap", "fail", `Missing ${path2.basename(bootstrapPath)}`);
814
+ const bootstrapPath = path5.join(projectDir, `${packageBaseName}.php`);
815
+ if (!fs5.existsSync(bootstrapPath)) {
816
+ return createDoctorCheck2("Ability bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
709
817
  }
710
- const source = fs2.readFileSync(bootstrapPath, "utf8");
818
+ const source = fs5.readFileSync(bootstrapPath, "utf8");
711
819
  const loadFunctionName = `${phpPrefix}_load_workflow_abilities`;
712
820
  const enqueueFunctionName = `${phpPrefix}_enqueue_workflow_abilities`;
713
821
  const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
@@ -724,14 +832,14 @@ function checkWorkspaceAbilityBootstrap(projectDir, packageName, phpPrefix) {
724
832
  }
725
833
  function checkWorkspaceAbilityIndex(projectDir, abilities) {
726
834
  const indexRelativePath = [
727
- path2.join("src", "abilities", "index.ts"),
728
- path2.join("src", "abilities", "index.js")
729
- ].find((relativePath) => fs2.existsSync(path2.join(projectDir, relativePath)));
835
+ path5.join("src", "abilities", "index.ts"),
836
+ path5.join("src", "abilities", "index.js")
837
+ ].find((relativePath) => fs5.existsSync(path5.join(projectDir, relativePath)));
730
838
  if (!indexRelativePath) {
731
839
  return createDoctorCheck2("Abilities index", "fail", "Missing src/abilities/index.ts or src/abilities/index.js");
732
840
  }
733
- const indexPath = path2.join(projectDir, indexRelativePath);
734
- const source = fs2.readFileSync(indexPath, "utf8");
841
+ const indexPath = path5.join(projectDir, indexRelativePath);
842
+ const source = fs5.readFileSync(indexPath, "utf8");
735
843
  const missingExports = abilities.filter((ability) => {
736
844
  const exportPattern = new RegExp(`^\\s*export\\s+(?:\\*\\s+from|\\{[^}]+\\}\\s+from)\\s+['"\`]\\./${escapeRegex(ability.slug)}\\/client['"\`]`, "mu");
737
845
  return !exportPattern.test(source);
@@ -742,9 +850,9 @@ function getWorkspaceAiFeatureRequiredFiles(aiFeature) {
742
850
  return Array.from(new Set([
743
851
  aiFeature.aiSchemaFile,
744
852
  aiFeature.apiFile,
745
- path2.join(path2.dirname(aiFeature.typesFile), "api-schemas", "feature-request.schema.json"),
746
- path2.join(path2.dirname(aiFeature.typesFile), "api-schemas", "feature-response.schema.json"),
747
- path2.join(path2.dirname(aiFeature.typesFile), "api-schemas", "feature-result.schema.json"),
853
+ path5.join(path5.dirname(aiFeature.typesFile), "api-schemas", "feature-request.schema.json"),
854
+ path5.join(path5.dirname(aiFeature.typesFile), "api-schemas", "feature-response.schema.json"),
855
+ path5.join(path5.dirname(aiFeature.typesFile), "api-schemas", "feature-result.schema.json"),
748
856
  aiFeature.clientFile,
749
857
  aiFeature.dataFile,
750
858
  aiFeature.openApiFile,
@@ -759,11 +867,11 @@ function checkWorkspaceAiFeatureConfig(aiFeature) {
759
867
  }
760
868
  function checkWorkspaceAiFeatureBootstrap(projectDir, packageName, phpPrefix) {
761
869
  const packageBaseName = packageName.split("/").pop() ?? packageName;
762
- const bootstrapPath = path2.join(projectDir, `${packageBaseName}.php`);
763
- if (!fs2.existsSync(bootstrapPath)) {
764
- return createDoctorCheck2("AI feature bootstrap", "fail", `Missing ${path2.basename(bootstrapPath)}`);
870
+ const bootstrapPath = path5.join(projectDir, `${packageBaseName}.php`);
871
+ if (!fs5.existsSync(bootstrapPath)) {
872
+ return createDoctorCheck2("AI feature bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
765
873
  }
766
- const source = fs2.readFileSync(bootstrapPath, "utf8");
874
+ const source = fs5.readFileSync(bootstrapPath, "utf8");
767
875
  const registerFunctionName = `${phpPrefix}_register_ai_features`;
768
876
  const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
769
877
  const hasServerGlob = source.includes(WORKSPACE_AI_FEATURE_GLOB);
@@ -771,14 +879,14 @@ function checkWorkspaceAiFeatureBootstrap(projectDir, packageName, phpPrefix) {
771
879
  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");
772
880
  }
773
881
  function getWorkspaceEditorPluginRequiredFiles(editorPlugin) {
774
- const editorPluginDir = path2.join("src", "editor-plugins", editorPlugin.slug);
775
- const surfaceFile = editorPlugin.slot === "PluginSidebar" ? path2.join(editorPluginDir, "Sidebar.tsx") : path2.join(editorPluginDir, "Surface.tsx");
882
+ const editorPluginDir = path5.join("src", "editor-plugins", editorPlugin.slug);
883
+ const surfaceFile = editorPlugin.slot === "PluginSidebar" ? path5.join(editorPluginDir, "Sidebar.tsx") : path5.join(editorPluginDir, "Surface.tsx");
776
884
  return Array.from(new Set([
777
885
  editorPlugin.file,
778
886
  surfaceFile,
779
- path2.join(editorPluginDir, "data.ts"),
780
- path2.join(editorPluginDir, "types.ts"),
781
- path2.join(editorPluginDir, "style.scss")
887
+ path5.join(editorPluginDir, "data.ts"),
888
+ path5.join(editorPluginDir, "types.ts"),
889
+ path5.join(editorPluginDir, "style.scss")
782
890
  ]));
783
891
  }
784
892
  function checkWorkspaceEditorPluginConfig(editorPlugin) {
@@ -788,11 +896,11 @@ function checkWorkspaceEditorPluginConfig(editorPlugin) {
788
896
  }
789
897
  function checkWorkspaceEditorPluginBootstrap(projectDir, packageName, phpPrefix) {
790
898
  const packageBaseName = packageName.split("/").pop() ?? packageName;
791
- const bootstrapPath = path2.join(projectDir, `${packageBaseName}.php`);
792
- if (!fs2.existsSync(bootstrapPath)) {
793
- return createDoctorCheck2("Editor plugin bootstrap", "fail", `Missing ${path2.basename(bootstrapPath)}`);
899
+ const bootstrapPath = path5.join(projectDir, `${packageBaseName}.php`);
900
+ if (!fs5.existsSync(bootstrapPath)) {
901
+ return createDoctorCheck2("Editor plugin bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
794
902
  }
795
- const source = fs2.readFileSync(bootstrapPath, "utf8");
903
+ const source = fs5.readFileSync(bootstrapPath, "utf8");
796
904
  const enqueueFunctionName = `${phpPrefix}_enqueue_editor_plugins_editor`;
797
905
  const enqueueHook = `add_action( 'enqueue_block_editor_assets', '${enqueueFunctionName}' );`;
798
906
  const hasEditorEnqueueHook = source.includes(enqueueHook);
@@ -803,14 +911,14 @@ function checkWorkspaceEditorPluginBootstrap(projectDir, packageName, phpPrefix)
803
911
  }
804
912
  function checkWorkspaceEditorPluginIndex(projectDir, editorPlugins) {
805
913
  const indexRelativePath = [
806
- path2.join("src", "editor-plugins", "index.ts"),
807
- path2.join("src", "editor-plugins", "index.js")
808
- ].find((relativePath) => fs2.existsSync(path2.join(projectDir, relativePath)));
914
+ path5.join("src", "editor-plugins", "index.ts"),
915
+ path5.join("src", "editor-plugins", "index.js")
916
+ ].find((relativePath) => fs5.existsSync(path5.join(projectDir, relativePath)));
809
917
  if (!indexRelativePath) {
810
918
  return createDoctorCheck2("Editor plugins index", "fail", "Missing src/editor-plugins/index.ts or src/editor-plugins/index.js");
811
919
  }
812
- const indexPath = path2.join(projectDir, indexRelativePath);
813
- const source = fs2.readFileSync(indexPath, "utf8");
920
+ const indexPath = path5.join(projectDir, indexRelativePath);
921
+ const source = fs5.readFileSync(indexPath, "utf8");
814
922
  const missingImports = editorPlugins.filter((editorPlugin) => {
815
923
  const importPattern = new RegExp(`['"\`]\\./${escapeRegex(editorPlugin.slug)}(?:/[^'"\`]*)?['"\`]`, "u");
816
924
  return !importPattern.test(source);
@@ -818,15 +926,15 @@ function checkWorkspaceEditorPluginIndex(projectDir, editorPlugins) {
818
926
  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(", ")}`);
819
927
  }
820
928
  function getWorkspaceAdminViewRequiredFiles(adminView) {
821
- const adminViewDir = path2.join("src", "admin-views", adminView.slug);
929
+ const adminViewDir = path5.join("src", "admin-views", adminView.slug);
822
930
  return Array.from(new Set([
823
931
  adminView.file,
824
932
  adminView.phpFile,
825
- path2.join(adminViewDir, "Screen.tsx"),
826
- path2.join(adminViewDir, "config.ts"),
827
- path2.join(adminViewDir, "data.ts"),
828
- path2.join(adminViewDir, "style.scss"),
829
- path2.join(adminViewDir, "types.ts")
933
+ path5.join(adminViewDir, "Screen.tsx"),
934
+ path5.join(adminViewDir, "config.ts"),
935
+ path5.join(adminViewDir, "data.ts"),
936
+ path5.join(adminViewDir, "style.scss"),
937
+ path5.join(adminViewDir, "types.ts")
830
938
  ]));
831
939
  }
832
940
  function checkWorkspaceAdminViewConfig(adminView, inventory) {
@@ -843,11 +951,11 @@ function checkWorkspaceAdminViewConfig(adminView, inventory) {
843
951
  }
844
952
  function checkWorkspaceAdminViewBootstrap(projectDir, packageName, phpPrefix) {
845
953
  const packageBaseName = packageName.split("/").pop() ?? packageName;
846
- const bootstrapPath = path2.join(projectDir, `${packageBaseName}.php`);
847
- if (!fs2.existsSync(bootstrapPath)) {
848
- return createDoctorCheck2("Admin view bootstrap", "fail", `Missing ${path2.basename(bootstrapPath)}`);
954
+ const bootstrapPath = path5.join(projectDir, `${packageBaseName}.php`);
955
+ if (!fs5.existsSync(bootstrapPath)) {
956
+ return createDoctorCheck2("Admin view bootstrap", "fail", `Missing ${path5.basename(bootstrapPath)}`);
849
957
  }
850
- const source = fs2.readFileSync(bootstrapPath, "utf8");
958
+ const source = fs5.readFileSync(bootstrapPath, "utf8");
851
959
  const loadFunctionName = `${phpPrefix}_load_admin_views`;
852
960
  const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
853
961
  const hasLoaderHook = source.includes(loadHook);
@@ -856,14 +964,14 @@ function checkWorkspaceAdminViewBootstrap(projectDir, packageName, phpPrefix) {
856
964
  }
857
965
  function checkWorkspaceAdminViewIndex(projectDir, adminViews) {
858
966
  const indexRelativePath = [
859
- path2.join("src", "admin-views", "index.ts"),
860
- path2.join("src", "admin-views", "index.js")
861
- ].find((relativePath) => fs2.existsSync(path2.join(projectDir, relativePath)));
967
+ path5.join("src", "admin-views", "index.ts"),
968
+ path5.join("src", "admin-views", "index.js")
969
+ ].find((relativePath) => fs5.existsSync(path5.join(projectDir, relativePath)));
862
970
  if (!indexRelativePath) {
863
971
  return createDoctorCheck2("Admin views index", "fail", "Missing src/admin-views/index.ts or src/admin-views/index.js");
864
972
  }
865
- const indexPath = path2.join(projectDir, indexRelativePath);
866
- const source = fs2.readFileSync(indexPath, "utf8");
973
+ const indexPath = path5.join(projectDir, indexRelativePath);
974
+ const source = fs5.readFileSync(indexPath, "utf8");
867
975
  const missingImports = adminViews.filter((adminView) => {
868
976
  const importPattern = new RegExp(`['"\`]\\./${escapeRegex(adminView.slug)}(?:/[^'"\`]*)?['"\`]`, "u");
869
977
  return !importPattern.test(source);
@@ -871,11 +979,11 @@ function checkWorkspaceAdminViewIndex(projectDir, adminViews) {
871
979
  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(", ")}`);
872
980
  }
873
981
  function checkWorkspaceAdminViewPhp(projectDir, adminView) {
874
- const phpPath = path2.join(projectDir, adminView.phpFile);
875
- if (!fs2.existsSync(phpPath)) {
982
+ const phpPath = path5.join(projectDir, adminView.phpFile);
983
+ if (!fs5.existsSync(phpPath)) {
876
984
  return createDoctorCheck2(`Admin view PHP ${adminView.slug}`, "fail", `Missing ${adminView.phpFile}`);
877
985
  }
878
- const source = fs2.readFileSync(phpPath, "utf8");
986
+ const source = fs5.readFileSync(phpPath, "utf8");
879
987
  const hasAdminMenu = source.includes("add_submenu_page");
880
988
  const hasAdminEnqueue = source.includes("admin_enqueue_scripts");
881
989
  const hasScript = source.includes(WORKSPACE_ADMIN_VIEW_SCRIPT);
@@ -884,56 +992,107 @@ function checkWorkspaceAdminViewPhp(projectDir, adminView) {
884
992
  const hasComponentsStyleDependency = source.includes("'wp-components'");
885
993
  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");
886
994
  }
887
- function checkVariationEntrypoint(projectDir, blockSlug) {
888
- const entryPath = path2.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
889
- if (!fs2.existsSync(entryPath)) {
890
- return createDoctorCheck2(`Variation entrypoint ${blockSlug}`, "fail", `Missing ${path2.relative(projectDir, entryPath)}`);
995
+ function getWorkspaceFeatureDoctorChecks(workspace, inventory) {
996
+ const checks = [];
997
+ if (inventory.restResources.length > 0) {
998
+ checks.push(checkWorkspaceRestResourceBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
891
999
  }
892
- const source = fs2.readFileSync(entryPath, "utf8");
893
- const hasImport = hasUncommentedPattern(source, WORKSPACE_VARIATIONS_IMPORT_PATTERN);
894
- const hasCall = hasExecutablePattern(source, WORKSPACE_VARIATIONS_CALL_PATTERN);
895
- return createDoctorCheck2(`Variation entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Variations registration hook is present" : "Missing ./variations import or registerWorkspaceVariations() call");
896
- }
897
- function checkBlockStyleEntrypoint(projectDir, blockSlug) {
898
- const entryPath = path2.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
899
- if (!fs2.existsSync(entryPath)) {
900
- return createDoctorCheck2(`Block style entrypoint ${blockSlug}`, "fail", `Missing ${path2.relative(projectDir, entryPath)}`);
1000
+ for (const restResource of inventory.restResources) {
1001
+ checks.push(checkWorkspaceRestResourceConfig(restResource));
1002
+ checks.push(checkExistingFiles(workspace.projectDir, `REST resource ${restResource.slug}`, getWorkspaceRestResourceRequiredFiles(restResource)));
901
1003
  }
902
- const source = fs2.readFileSync(entryPath, "utf8");
903
- const hasImport = hasUncommentedPattern(source, WORKSPACE_BLOCK_STYLES_IMPORT_PATTERN);
904
- const hasCall = hasExecutablePattern(source, WORKSPACE_BLOCK_STYLES_CALL_PATTERN);
905
- return createDoctorCheck2(`Block style entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Block style registration hook is present" : "Missing ./styles import or registerWorkspaceBlockStyles() call");
906
- }
907
- function checkBlockTransformEntrypoint(projectDir, blockSlug) {
908
- const entryPath = path2.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
909
- if (!fs2.existsSync(entryPath)) {
910
- return createDoctorCheck2(`Block transform entrypoint ${blockSlug}`, "fail", `Missing ${path2.relative(projectDir, entryPath)}`);
1004
+ if (inventory.abilities.length > 0) {
1005
+ checks.push(checkWorkspaceAbilityBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1006
+ checks.push(checkWorkspaceAbilityIndex(workspace.projectDir, inventory.abilities));
911
1007
  }
912
- const source = fs2.readFileSync(entryPath, "utf8");
913
- const hasImport = hasUncommentedPattern(source, WORKSPACE_BLOCK_TRANSFORMS_IMPORT_PATTERN);
914
- const hasCall = hasExecutablePattern(source, WORKSPACE_BLOCK_TRANSFORMS_CALL_PATTERN);
915
- 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");
1008
+ for (const ability of inventory.abilities) {
1009
+ checks.push(checkWorkspaceAbilityConfig(workspace.projectDir, ability));
1010
+ checks.push(checkExistingFiles(workspace.projectDir, `Ability ${ability.slug}`, getWorkspaceAbilityRequiredFiles(ability)));
1011
+ }
1012
+ if (inventory.aiFeatures.length > 0) {
1013
+ checks.push(checkWorkspaceAiFeatureBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1014
+ }
1015
+ for (const aiFeature of inventory.aiFeatures) {
1016
+ checks.push(checkWorkspaceAiFeatureConfig(aiFeature));
1017
+ checks.push(checkExistingFiles(workspace.projectDir, `AI feature ${aiFeature.slug}`, getWorkspaceAiFeatureRequiredFiles(aiFeature)));
1018
+ }
1019
+ if (inventory.editorPlugins.length > 0) {
1020
+ checks.push(checkWorkspaceEditorPluginBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1021
+ checks.push(checkWorkspaceEditorPluginIndex(workspace.projectDir, inventory.editorPlugins));
1022
+ }
1023
+ for (const editorPlugin of inventory.editorPlugins) {
1024
+ checks.push(checkExistingFiles(workspace.projectDir, `Editor plugin ${editorPlugin.slug}`, getWorkspaceEditorPluginRequiredFiles(editorPlugin)));
1025
+ checks.push(checkWorkspaceEditorPluginConfig(editorPlugin));
1026
+ }
1027
+ if (inventory.adminViews.length > 0) {
1028
+ checks.push(checkWorkspaceAdminViewBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1029
+ checks.push(checkWorkspaceAdminViewIndex(workspace.projectDir, inventory.adminViews));
1030
+ }
1031
+ for (const adminView of inventory.adminViews) {
1032
+ checks.push(checkWorkspaceAdminViewConfig(adminView, inventory));
1033
+ checks.push(checkExistingFiles(workspace.projectDir, `Admin view ${adminView.slug}`, getWorkspaceAdminViewRequiredFiles(adminView)));
1034
+ checks.push(checkWorkspaceAdminViewPhp(workspace.projectDir, adminView));
1035
+ }
1036
+ return checks;
916
1037
  }
917
- function checkBlockTransformConfig(workspace, transform) {
918
- const expectedTo = `${workspace.workspace.namespace}/${transform.block}`;
1038
+
1039
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace-package.ts
1040
+ import fs6 from "fs";
1041
+ import path6 from "path";
1042
+ function getWorkspacePackageMetadataCheck(workspace, packageJson) {
919
1043
  const issues = [];
920
- if (!WORKSPACE_FULL_BLOCK_NAME_PATTERN.test(transform.from)) {
921
- issues.push("from must use full namespace/block format");
1044
+ const packageName = packageJson.name;
1045
+ const bootstrapRelativePath = getWorkspaceBootstrapRelativePath(typeof packageName === "string" && packageName.length > 0 ? packageName : workspace.packageName);
1046
+ const wpTypia = packageJson.wpTypia;
1047
+ if (typeof packageName !== "string" || packageName.length === 0) {
1048
+ issues.push("package.json must define a string name for workspace bootstrap resolution");
922
1049
  }
923
- if (transform.to !== expectedTo) {
924
- issues.push(`to must equal "${expectedTo}" for workspace block "${transform.block}"`);
1050
+ if (wpTypia?.projectType !== "workspace") {
1051
+ issues.push('wpTypia.projectType must be "workspace"');
925
1052
  }
926
- 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("; "));
1053
+ if (wpTypia?.templatePackage !== WORKSPACE_TEMPLATE_PACKAGE) {
1054
+ issues.push(`wpTypia.templatePackage must be "${WORKSPACE_TEMPLATE_PACKAGE}"`);
1055
+ }
1056
+ if (wpTypia?.namespace !== workspace.workspace.namespace) {
1057
+ issues.push(`wpTypia.namespace must equal "${workspace.workspace.namespace}"`);
1058
+ }
1059
+ if (wpTypia?.textDomain !== workspace.workspace.textDomain) {
1060
+ issues.push(`wpTypia.textDomain must equal "${workspace.workspace.textDomain}"`);
1061
+ }
1062
+ if (wpTypia?.phpPrefix !== workspace.workspace.phpPrefix) {
1063
+ issues.push(`wpTypia.phpPrefix must equal "${workspace.workspace.phpPrefix}"`);
1064
+ }
1065
+ if (!fs6.existsSync(path6.join(workspace.projectDir, bootstrapRelativePath))) {
1066
+ issues.push(`Missing bootstrap file ${bootstrapRelativePath}`);
1067
+ }
1068
+ return createDoctorCheck2("Workspace package metadata", issues.length === 0 ? "pass" : "fail", issues.length === 0 ? `package.json metadata aligns with ${workspace.packageName} and ${bootstrapRelativePath}` : issues.join("; "));
927
1069
  }
928
- function checkMigrationWorkspaceHint(workspace, packageJson) {
1070
+ function getMigrationWorkspaceHintCheck(workspace, packageJson) {
929
1071
  const hasMigrationScript = typeof packageJson.scripts?.["migration:doctor"] === "string";
930
- const migrationConfigRelativePath = path2.join("src", "migrations", "config.ts");
931
- const hasMigrationConfig = fs2.existsSync(path2.join(workspace.projectDir, migrationConfigRelativePath));
1072
+ const migrationConfigRelativePath = path6.join("src", "migrations", "config.ts");
1073
+ const hasMigrationConfig = fs6.existsSync(path6.join(workspace.projectDir, migrationConfigRelativePath));
932
1074
  if (!hasMigrationScript && !hasMigrationConfig) {
933
1075
  return null;
934
1076
  }
935
1077
  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`);
936
1078
  }
1079
+
1080
+ // ../wp-typia-project-tools/src/runtime/cli-doctor-workspace.ts
1081
+ function formatWorkspaceInventorySummary(inventory) {
1082
+ return [
1083
+ `${inventory.blocks.length} block(s)`,
1084
+ `${inventory.variations.length} variation(s)`,
1085
+ `${inventory.blockStyles.length} block style(s)`,
1086
+ `${inventory.blockTransforms.length} block transform(s)`,
1087
+ `${inventory.patterns.length} pattern(s)`,
1088
+ `${inventory.bindingSources.length} binding source(s)`,
1089
+ `${inventory.restResources.length} REST resource(s)`,
1090
+ `${inventory.abilities.length} ability scaffold(s)`,
1091
+ `${inventory.aiFeatures.length} AI feature(s)`,
1092
+ `${inventory.editorPlugins.length} editor plugin(s)`,
1093
+ `${inventory.adminViews.length} admin view(s)`
1094
+ ].join(", ");
1095
+ }
937
1096
  function getWorkspaceDoctorChecks(cwd) {
938
1097
  const checks = [];
939
1098
  let workspace = null;
@@ -963,122 +1122,14 @@ function getWorkspaceDoctorChecks(cwd) {
963
1122
  checks.push(createDoctorCheck2("Workspace package metadata", "fail", error instanceof Error ? error.message : String(error)));
964
1123
  return checks;
965
1124
  }
966
- checks.push(checkWorkspacePackageMetadata(workspace, workspacePackageJson));
1125
+ checks.push(getWorkspacePackageMetadataCheck(workspace, workspacePackageJson));
967
1126
  try {
968
1127
  const inventory = readWorkspaceInventory(workspace.projectDir);
969
- checks.push(createDoctorCheck2("Workspace inventory", "pass", `${inventory.blocks.length} block(s), ${inventory.variations.length} variation(s), ${inventory.blockStyles.length} block style(s), ${inventory.blockTransforms.length} block transform(s), ${inventory.patterns.length} pattern(s), ${inventory.bindingSources.length} binding source(s), ${inventory.restResources.length} REST resource(s), ${inventory.abilities.length} ability scaffold(s), ${inventory.aiFeatures.length} AI feature(s), ${inventory.editorPlugins.length} editor plugin(s), ${inventory.adminViews.length} admin view(s)`));
970
- for (const block of inventory.blocks) {
971
- checks.push(checkExistingFiles(workspace.projectDir, `Block ${block.slug}`, getWorkspaceBlockRequiredFiles(block)));
972
- checks.push(checkWorkspaceBlockMetadata(workspace.projectDir, workspace, block));
973
- checks.push(checkWorkspaceBlockHooks(workspace.projectDir, block.slug));
974
- checks.push(checkWorkspaceBlockCollectionImport(workspace.projectDir, block.slug));
975
- checks.push(...checkWorkspaceBlockIframeCompatibility(workspace.projectDir, block.slug));
976
- }
977
- const registeredBlockSlugs = new Set(inventory.blocks.map((block) => block.slug));
978
- const variationTargetBlocks = new Set;
979
- for (const variation of inventory.variations) {
980
- if (!registeredBlockSlugs.has(variation.block)) {
981
- checks.push(createDoctorCheck2(`Variation ${variation.block}/${variation.slug}`, "fail", `Variation references unknown block "${variation.block}"`));
982
- continue;
983
- }
984
- variationTargetBlocks.add(variation.block);
985
- checks.push(checkExistingFiles(workspace.projectDir, `Variation ${variation.block}/${variation.slug}`, [variation.file]));
986
- }
987
- for (const blockSlug of variationTargetBlocks) {
988
- checks.push(checkVariationEntrypoint(workspace.projectDir, blockSlug));
989
- }
990
- const blockStyleTargetBlocks = new Set;
991
- for (const blockStyle of inventory.blockStyles) {
992
- if (!registeredBlockSlugs.has(blockStyle.block)) {
993
- checks.push(createDoctorCheck2(`Block style ${blockStyle.block}/${blockStyle.slug}`, "fail", `Block style references unknown block "${blockStyle.block}"`));
994
- continue;
995
- }
996
- blockStyleTargetBlocks.add(blockStyle.block);
997
- checks.push(checkExistingFiles(workspace.projectDir, `Block style ${blockStyle.block}/${blockStyle.slug}`, [blockStyle.file]));
998
- }
999
- for (const blockSlug of blockStyleTargetBlocks) {
1000
- checks.push(checkExistingFiles(workspace.projectDir, `Block style registry ${blockSlug}`, [
1001
- path2.join("src", "blocks", blockSlug, "styles", "index.ts")
1002
- ]));
1003
- checks.push(checkBlockStyleEntrypoint(workspace.projectDir, blockSlug));
1004
- }
1005
- const blockTransformTargetBlocks = new Set;
1006
- for (const blockTransform of inventory.blockTransforms) {
1007
- if (!registeredBlockSlugs.has(blockTransform.block)) {
1008
- checks.push(createDoctorCheck2(`Block transform ${blockTransform.block}/${blockTransform.slug}`, "fail", `Block transform references unknown block "${blockTransform.block}"`));
1009
- continue;
1010
- }
1011
- blockTransformTargetBlocks.add(blockTransform.block);
1012
- checks.push(checkBlockTransformConfig(workspace, blockTransform));
1013
- checks.push(checkExistingFiles(workspace.projectDir, `Block transform ${blockTransform.block}/${blockTransform.slug}`, [blockTransform.file]));
1014
- }
1015
- for (const blockSlug of blockTransformTargetBlocks) {
1016
- checks.push(checkExistingFiles(workspace.projectDir, `Block transform registry ${blockSlug}`, [
1017
- path2.join("src", "blocks", blockSlug, "transforms", "index.ts")
1018
- ]));
1019
- checks.push(checkBlockTransformEntrypoint(workspace.projectDir, blockSlug));
1020
- }
1021
- const shouldCheckPatternBootstrap = inventory.patterns.length > 0 || fs2.existsSync(path2.join(workspace.projectDir, "src", "patterns"));
1022
- if (shouldCheckPatternBootstrap) {
1023
- checks.push(checkWorkspacePatternBootstrap(workspace.projectDir, workspace.packageName));
1024
- }
1025
- for (const pattern of inventory.patterns) {
1026
- checks.push(checkExistingFiles(workspace.projectDir, `Pattern ${pattern.slug}`, [pattern.file]));
1027
- }
1028
- if (inventory.bindingSources.length > 0) {
1029
- checks.push(checkWorkspaceBindingBootstrap(workspace.projectDir, workspace.packageName));
1030
- checks.push(checkWorkspaceBindingSourcesIndex(workspace.projectDir, inventory.bindingSources));
1031
- }
1032
- for (const bindingSource of inventory.bindingSources) {
1033
- checks.push(checkExistingFiles(workspace.projectDir, `Binding source ${bindingSource.slug}`, [
1034
- bindingSource.serverFile,
1035
- bindingSource.editorFile
1036
- ]));
1037
- const bindingTargetCheck = checkWorkspaceBindingTarget(workspace.projectDir, workspace, registeredBlockSlugs, bindingSource);
1038
- if (bindingTargetCheck) {
1039
- checks.push(bindingTargetCheck);
1040
- }
1041
- }
1042
- if (inventory.restResources.length > 0) {
1043
- checks.push(checkWorkspaceRestResourceBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1044
- }
1045
- for (const restResource of inventory.restResources) {
1046
- checks.push(checkWorkspaceRestResourceConfig(restResource));
1047
- checks.push(checkExistingFiles(workspace.projectDir, `REST resource ${restResource.slug}`, getWorkspaceRestResourceRequiredFiles(restResource)));
1048
- }
1049
- if (inventory.abilities.length > 0) {
1050
- checks.push(checkWorkspaceAbilityBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1051
- checks.push(checkWorkspaceAbilityIndex(workspace.projectDir, inventory.abilities));
1052
- }
1053
- for (const ability of inventory.abilities) {
1054
- checks.push(checkWorkspaceAbilityConfig(workspace.projectDir, ability));
1055
- checks.push(checkExistingFiles(workspace.projectDir, `Ability ${ability.slug}`, getWorkspaceAbilityRequiredFiles(ability)));
1056
- }
1057
- if (inventory.aiFeatures.length > 0) {
1058
- checks.push(checkWorkspaceAiFeatureBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1059
- }
1060
- for (const aiFeature of inventory.aiFeatures) {
1061
- checks.push(checkWorkspaceAiFeatureConfig(aiFeature));
1062
- checks.push(checkExistingFiles(workspace.projectDir, `AI feature ${aiFeature.slug}`, getWorkspaceAiFeatureRequiredFiles(aiFeature)));
1063
- }
1064
- if (inventory.editorPlugins.length > 0) {
1065
- checks.push(checkWorkspaceEditorPluginBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1066
- checks.push(checkWorkspaceEditorPluginIndex(workspace.projectDir, inventory.editorPlugins));
1067
- }
1068
- for (const editorPlugin of inventory.editorPlugins) {
1069
- checks.push(checkExistingFiles(workspace.projectDir, `Editor plugin ${editorPlugin.slug}`, getWorkspaceEditorPluginRequiredFiles(editorPlugin)));
1070
- checks.push(checkWorkspaceEditorPluginConfig(editorPlugin));
1071
- }
1072
- if (inventory.adminViews.length > 0) {
1073
- checks.push(checkWorkspaceAdminViewBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
1074
- checks.push(checkWorkspaceAdminViewIndex(workspace.projectDir, inventory.adminViews));
1075
- }
1076
- for (const adminView of inventory.adminViews) {
1077
- checks.push(checkWorkspaceAdminViewConfig(adminView, inventory));
1078
- checks.push(checkExistingFiles(workspace.projectDir, `Admin view ${adminView.slug}`, getWorkspaceAdminViewRequiredFiles(adminView)));
1079
- checks.push(checkWorkspaceAdminViewPhp(workspace.projectDir, adminView));
1080
- }
1081
- const migrationWorkspaceCheck = checkMigrationWorkspaceHint(workspace, workspacePackageJson);
1128
+ checks.push(createDoctorCheck2("Workspace inventory", "pass", formatWorkspaceInventorySummary(inventory)));
1129
+ checks.push(...getWorkspaceBlockDoctorChecks(workspace, inventory));
1130
+ checks.push(...getWorkspaceBindingDoctorChecks(workspace, inventory));
1131
+ checks.push(...getWorkspaceFeatureDoctorChecks(workspace, inventory));
1132
+ const migrationWorkspaceCheck = getMigrationWorkspaceHintCheck(workspace, workspacePackageJson);
1082
1133
  if (migrationWorkspaceCheck) {
1083
1134
  checks.push(migrationWorkspaceCheck);
1084
1135
  }
@@ -1121,4 +1172,4 @@ export {
1121
1172
  getDoctorChecks
1122
1173
  };
1123
1174
 
1124
- //# debugId=0A56FB55109F0ECC64756E2164756E21
1175
+ //# debugId=A8225043978E779064756E2164756E21