wp-typia 0.22.2 → 0.22.4

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