wp-typia 0.20.5 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,28 +1,34 @@
1
1
  // @bun
2
2
  import {
3
3
  CLI_DIAGNOSTIC_CODES,
4
+ CLI_DIAGNOSTIC_CODE_METADATA,
4
5
  CliDiagnosticError,
5
6
  createCliCommandError,
7
+ createCliDiagnosticCodeError,
6
8
  formatCliDiagnosticError,
7
9
  formatDoctorCheckLine,
8
10
  formatDoctorSummaryLine,
11
+ getCliDiagnosticCodeMetadata,
9
12
  getDoctorFailureDetailLines,
10
13
  getFailingDoctorChecks,
11
14
  isCliDiagnosticError,
12
15
  serializeCliDiagnosticError
13
- } from "./cli-xxzpb58s.js";
16
+ } from "./cli-p95wr1q8.js";
14
17
  import"./cli-xnn9xjcy.js";
15
18
  export {
16
19
  serializeCliDiagnosticError,
17
20
  isCliDiagnosticError,
18
21
  getFailingDoctorChecks,
19
22
  getDoctorFailureDetailLines,
23
+ getCliDiagnosticCodeMetadata,
20
24
  formatDoctorSummaryLine,
21
25
  formatDoctorCheckLine,
22
26
  formatCliDiagnosticError,
27
+ createCliDiagnosticCodeError,
23
28
  createCliCommandError,
24
29
  CliDiagnosticError,
30
+ CLI_DIAGNOSTIC_CODE_METADATA,
25
31
  CLI_DIAGNOSTIC_CODES
26
32
  };
27
33
 
28
- //# debugId=D7A37B51FF87D98264756E2164756E21
34
+ //# debugId=C0BB65021DB87CDD64756E2164756E21
@@ -1,10 +1,4 @@
1
1
  // @bun
2
- import {
3
- createCliCommandError,
4
- formatDoctorCheckLine,
5
- formatDoctorSummaryLine,
6
- getDoctorFailureDetailLines
7
- } from "./cli-xxzpb58s.js";
8
2
  import {
9
3
  getBuiltInTemplateLayerDirs,
10
4
  isOmittableBuiltInTemplateLayerDir
@@ -19,9 +13,18 @@ import {
19
13
  HOOKED_BLOCK_POSITION_SET,
20
14
  REST_RESOURCE_METHOD_IDS,
21
15
  REST_RESOURCE_NAMESPACE_PATTERN,
22
- readWorkspaceInventory
23
- } from "./cli-3w3qxq9w.js";
16
+ escapeRegex,
17
+ readWorkspaceInventory,
18
+ resolveEditorPluginSlotAlias
19
+ } from "./cli-j180bk07.js";
24
20
  import"./cli-t73q5aqz.js";
21
+ import {
22
+ CLI_DIAGNOSTIC_CODES,
23
+ createCliCommandError,
24
+ formatDoctorCheckLine,
25
+ formatDoctorSummaryLine,
26
+ getDoctorFailureDetailLines
27
+ } from "./cli-p95wr1q8.js";
25
28
  import {
26
29
  WORKSPACE_TEMPLATE_PACKAGE,
27
30
  getInvalidWorkspaceProjectReason,
@@ -140,6 +143,10 @@ var WORKSPACE_ABILITY_GLOB = "/inc/abilities/*.php";
140
143
  var WORKSPACE_ABILITY_EDITOR_SCRIPT = "build/abilities/index.js";
141
144
  var WORKSPACE_ABILITY_EDITOR_ASSET = "build/abilities/index.asset.php";
142
145
  var WORKSPACE_AI_FEATURE_GLOB = "/inc/ai-features/*.php";
146
+ var WORKSPACE_ADMIN_VIEW_GLOB = "/inc/admin-views/*.php";
147
+ var WORKSPACE_ADMIN_VIEW_SCRIPT = "build/admin-views/index.js";
148
+ var WORKSPACE_ADMIN_VIEW_ASSET = "build/admin-views/index.asset.php";
149
+ var WORKSPACE_ADMIN_VIEW_STYLE = "build/admin-views/style-index.css";
143
150
  var WORKSPACE_EDITOR_PLUGIN_EDITOR_SCRIPT = "build/editor-plugins/index.js";
144
151
  var WORKSPACE_EDITOR_PLUGIN_EDITOR_ASSET = "build/editor-plugins/index.asset.php";
145
152
  var WORKSPACE_EDITOR_PLUGIN_EDITOR_STYLE = "build/editor-plugins/style-index.css";
@@ -150,14 +157,244 @@ var WORKSPACE_GENERATED_BLOCK_ARTIFACTS = [
150
157
  "typia-validator.php",
151
158
  "typia.openapi.json"
152
159
  ];
153
- function createDoctorCheck2(label, status, detail) {
154
- return { detail, label, status };
160
+ var WORKSPACE_FULL_BLOCK_NAME_PATTERN = /^[a-z0-9-]+\/[a-z0-9-]+$/u;
161
+ var WORKSPACE_VARIATIONS_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceVariations\s*\}\s*from\s*["']\.\/variations["']\s*;?\s*$/mu;
162
+ var WORKSPACE_VARIATIONS_CALL_PATTERN = /registerWorkspaceVariations\s*\(\s*\)\s*;?/u;
163
+ var WORKSPACE_BLOCK_STYLES_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceBlockStyles\s*\}\s*from\s*["']\.\/styles["']\s*;?\s*$/mu;
164
+ var WORKSPACE_BLOCK_STYLES_CALL_PATTERN = /registerWorkspaceBlockStyles\s*\(\s*\)\s*;?/u;
165
+ var WORKSPACE_BLOCK_TRANSFORMS_IMPORT_PATTERN = /^\s*import\s*\{\s*applyWorkspaceBlockTransforms\s*\}\s*from\s*["']\.\/transforms["']\s*;?\s*$/mu;
166
+ var WORKSPACE_BLOCK_TRANSFORMS_CALL_PATTERN = /applyWorkspaceBlockTransforms\s*\(\s*registration\s*\.\s*settings\s*\)\s*;?/u;
167
+ var WORKSPACE_BLOCK_IFRAME_COMPATIBILITY_DOC_URL = "https://developer.wordpress.org/block-editor/reference-guides/block-api/block-api-versions/block-migration-for-iframe-editor-compatibility/";
168
+ var WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES = {
169
+ API_VERSION: "wp-typia.workspace.block.iframe.api-version",
170
+ BLOCK_PROPS: "wp-typia.workspace.block.iframe.block-props",
171
+ EDITOR_GLOBALS: "wp-typia.workspace.block.iframe.editor-globals",
172
+ EDITOR_STYLES: "wp-typia.workspace.block.iframe.editor-styles"
173
+ };
174
+ var WORKSPACE_BLOCK_EDITOR_SOURCE_FILE_PATTERN = /\.[cm]?[jt]sx?$/u;
175
+ var WORKSPACE_BLOCK_EDITOR_SOURCE_BASENAMES = new Set([
176
+ "edit",
177
+ "editor",
178
+ "index",
179
+ "save"
180
+ ]);
181
+ var WORKSPACE_BLOCK_EDITOR_SOURCE_DIRECTORIES = new Set([
182
+ "components",
183
+ "controls",
184
+ "editor",
185
+ "inspector"
186
+ ]);
187
+ var WORKSPACE_BLOCK_LOCAL_STYLE_FILES = [
188
+ "editor.css",
189
+ "editor.scss",
190
+ "index.css",
191
+ "style.css",
192
+ "style.scss"
193
+ ];
194
+ var WORKSPACE_BLOCK_IFRAME_GLOBAL_DOM_PATTERN = /\b(?:document|window)\b|\b(?:parent|top)\b(?!\s*:)/gu;
195
+ 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 };
155
198
  }
156
199
  function createDoctorScopeCheck(status, detail) {
157
200
  return createDoctorCheck2("Doctor scope", status, detail);
158
201
  }
159
- function escapeRegex(value) {
160
- return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
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
+ function normalizePathSeparators(relativePath) {
264
+ return relativePath.split(path2.sep).join("/");
265
+ }
266
+ function hasRegisteredBlockAsset(value) {
267
+ if (typeof value === "string") {
268
+ return value.trim().length > 0;
269
+ }
270
+ if (Array.isArray(value)) {
271
+ return value.some((entry) => hasRegisteredBlockAsset(entry));
272
+ }
273
+ return false;
274
+ }
275
+ 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)) {
279
+ return {
280
+ blockJsonRelativePath,
281
+ error: `Missing ${blockJsonRelativePath}`
282
+ };
283
+ }
284
+ 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
+ }
292
+ return {
293
+ blockJsonRelativePath,
294
+ document: parsed
295
+ };
296
+ } catch (error) {
297
+ return {
298
+ blockJsonRelativePath,
299
+ error: error instanceof Error ? error.message : String(error)
300
+ };
301
+ }
302
+ }
303
+ 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)) {
307
+ return false;
308
+ }
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)) {
313
+ return true;
314
+ }
315
+ return segments.slice(0, -1).some((segment) => WORKSPACE_BLOCK_EDITOR_SOURCE_DIRECTORIES.has(segment));
316
+ }
317
+ function isWorkspaceBlockSaveSource(relativePath) {
318
+ const fileName = normalizePathSeparators(relativePath).split("/").pop() ?? "";
319
+ return fileName.replace(/\.[^.]+$/u, "").toLowerCase() === "save";
320
+ }
321
+ function collectWorkspaceBlockEditorSources(projectDir, blockSlug) {
322
+ const blockDir = path2.join(projectDir, "src", "blocks", blockSlug);
323
+ if (!fs2.existsSync(blockDir)) {
324
+ return [];
325
+ }
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);
330
+ if (entry.isDirectory()) {
331
+ visitDirectory(entryPath);
332
+ continue;
333
+ }
334
+ if (!entry.isFile()) {
335
+ continue;
336
+ }
337
+ const relativePath = normalizePathSeparators(path2.relative(projectDir, entryPath));
338
+ const blockRelativePath = path2.relative(blockDir, entryPath);
339
+ if (!isWorkspaceBlockEditorSource(blockRelativePath)) {
340
+ continue;
341
+ }
342
+ sources.push({
343
+ relativePath,
344
+ source: fs2.readFileSync(entryPath, "utf8")
345
+ });
346
+ }
347
+ };
348
+ visitDirectory(blockDir);
349
+ return sources;
350
+ }
351
+ function getSourceLineNumber(source, index) {
352
+ let lineNumber = 1;
353
+ for (let cursor = 0;cursor < index; cursor += 1) {
354
+ if (source[cursor] === `
355
+ `) {
356
+ lineNumber += 1;
357
+ }
358
+ }
359
+ return lineNumber;
360
+ }
361
+ function isGlobalDomAccessCandidate(maskedSource, index, token) {
362
+ if (token === "document" || token === "window") {
363
+ return true;
364
+ }
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 === ",") {
370
+ return false;
371
+ }
372
+ if (nextNonWhitespace === ":") {
373
+ return false;
374
+ }
375
+ if (/\b(?:const|function|let|var)\s+$/u.test(before)) {
376
+ return false;
377
+ }
378
+ return true;
379
+ }
380
+ function findWorkspaceBlockGlobalDomAccesses(sources) {
381
+ const findings = [];
382
+ for (const { relativePath, source } of sources) {
383
+ const maskedSource = maskTypeScriptCommentsAndLiterals(source);
384
+ const matches = maskedSource.matchAll(WORKSPACE_BLOCK_IFRAME_GLOBAL_DOM_PATTERN);
385
+ 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)) {
389
+ continue;
390
+ }
391
+ findings.push(`${relativePath}:${getSourceLineNumber(source, index)} (${matchedToken})`);
392
+ if (findings.length >= 5) {
393
+ return findings;
394
+ }
395
+ }
396
+ }
397
+ return findings;
161
398
  }
162
399
  function getWorkspaceBootstrapRelativePath(packageName) {
163
400
  const packageBaseName = packageName.split("/").pop() ?? packageName;
@@ -260,6 +497,32 @@ function checkWorkspaceBlockCollectionImport(projectDir, blockSlug) {
260
497
  const hasCollectionImport = WORKSPACE_COLLECTION_IMPORT_PATTERN.test(source);
261
498
  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}`);
262
499
  }
500
+ function checkWorkspaceBlockIframeCompatibility(projectDir, blockSlug) {
501
+ const metadataResult = readWorkspaceBlockIframeMetadata(projectDir, blockSlug);
502
+ if (!metadataResult.document) {
503
+ return [
504
+ createDoctorCheck2(`Block iframe/API v3 ${blockSlug}`, "warn", metadataResult.error ?? `Unable to inspect ${metadataResult.blockJsonRelativePath}`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.API_VERSION)
505
+ ];
506
+ }
507
+ const blockJson = metadataResult.document;
508
+ 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)));
511
+ const hasRegisteredEditorStyles = hasRegisteredBlockAsset(blockJson.style) || hasRegisteredBlockAsset(blockJson.editorStyle);
512
+ const editorSources = collectWorkspaceBlockEditorSources(projectDir, blockSlug);
513
+ const editorWrapperSources = editorSources.filter((source) => !isWorkspaceBlockSaveSource(source.relativePath));
514
+ const globalDomAccesses = findWorkspaceBlockGlobalDomAccesses(editorSources);
515
+ const hasBlockPropsUsage = editorSources.some(({ source }) => hasExecutablePattern(source, WORKSPACE_BLOCK_PROPS_PATTERN));
516
+ const hasEditorBlockPropsUsage = editorWrapperSources.some(({ source }) => hasExecutablePattern(source, WORKSPACE_BLOCK_PROPS_PATTERN));
517
+ const blockWrapperStatus = editorWrapperSources.length === 0 || hasEditorBlockPropsUsage ? "pass" : "warn";
518
+ const blockWrapperDetail = editorSources.length === 0 ? "No editor-facing block source files found; general file checks will report missing entrypoints" : editorWrapperSources.length === 0 ? "No editor wrapper source files found; general file checks will report missing entrypoints" : hasEditorBlockPropsUsage ? "Editor-facing sources use block wrapper props" : hasBlockPropsUsage ? "Only save-facing useBlockProps.save() usage was detected. Confirm the editor wrapper also receives useBlockProps() or useInnerBlocksProps() before relying on iframe editor rendering." : "No useBlockProps(), useBlockProps.save(), or useInnerBlocksProps() usage was detected in editor-facing sources. Confirm the block wrapper receives WordPress block editor props before relying on iframe editor rendering.";
519
+ return [
520
+ createDoctorCheck2(`Block iframe API version ${blockSlug}`, apiVersion !== null && apiVersion >= 3 ? "pass" : "warn", apiVersion !== null && apiVersion >= 3 ? "block.json declares apiVersion 3 for iframe editor readiness" : `Set ${metadataResult.blockJsonRelativePath} apiVersion to 3 after testing the block in iframe-enabled Post Editor and Site Editor contexts. WordPress recommends API v3 for iframe editor compatibility. See ${WORKSPACE_BLOCK_IFRAME_COMPATIBILITY_DOC_URL}`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.API_VERSION),
521
+ createDoctorCheck2(`Block iframe styles ${blockSlug}`, localStyleFiles.length === 0 || hasRegisteredEditorStyles ? "pass" : "warn", localStyleFiles.length === 0 ? "No local block stylesheet source files found to register" : hasRegisteredEditorStyles ? "block.json registers block styles for iframe editor loading" : `Found stylesheet source files (${localStyleFiles.join(", ")}) but block.json does not declare style or editorStyle. Register block content styles so iframe editors do not depend on parent admin styles.`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.EDITOR_STYLES),
522
+ createDoctorCheck2(`Block iframe globals ${blockSlug}`, globalDomAccesses.length === 0 ? "pass" : "warn", globalDomAccesses.length === 0 ? "No direct window/document/parent DOM access detected in editor-facing block sources" : `Direct global DOM access detected at ${globalDomAccesses.join(", ")}. Prefer element.ownerDocument/defaultView via refs or useRefEffect for iframe editor content.`, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.EDITOR_GLOBALS),
523
+ createDoctorCheck2(`Block iframe wrapper ${blockSlug}`, blockWrapperStatus, blockWrapperDetail, WORKSPACE_BLOCK_IFRAME_DIAGNOSTIC_CODES.BLOCK_PROPS)
524
+ ];
525
+ }
263
526
  function checkWorkspacePatternBootstrap(projectDir, packageName) {
264
527
  const packageBaseName = packageName.split("/").pop() ?? packageName;
265
528
  const bootstrapPath = path2.join(projectDir, `${packageBaseName}.php`);
@@ -294,6 +557,71 @@ function checkWorkspaceBindingSourcesIndex(projectDir, bindingSources) {
294
557
  const missingImports = bindingSources.filter((bindingSource) => !source.includes(`./${bindingSource.slug}/editor`));
295
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(", ")}`);
296
559
  }
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;
565
+ }
566
+ if (!bindingSource.block || !bindingSource.attribute) {
567
+ return createDoctorCheck2(`Binding target ${bindingSource.slug}`, "fail", "Binding target entries must include both block and attribute.");
568
+ }
569
+ if (!registeredBlockSlugs.has(bindingSource.block)) {
570
+ return createDoctorCheck2(`Binding target ${bindingSource.slug}`, "fail", `Binding target references unknown block "${bindingSource.block}".`);
571
+ }
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
+ }
585
+ }
586
+ } catch (error) {
587
+ issues.push(error instanceof Error ? `Unable to read ${blockJsonRelativePath}: ${error.message}` : `Unable to read ${blockJsonRelativePath}.`);
588
+ }
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}`);
595
+ }
596
+ if (!new RegExp(`'${escapeRegex(bindingSource.attribute)}'`, "u").test(serverSource)) {
597
+ issues.push(`${bindingSource.serverFile} must expose attribute "${bindingSource.attribute}"`);
598
+ }
599
+ } else {
600
+ issues.push(`Missing ${bindingSource.serverFile}`);
601
+ }
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}`);
622
+ }
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("; "));
624
+ }
297
625
  function getWorkspaceRestResourceRequiredFiles(restResource) {
298
626
  const schemaNames = new Set;
299
627
  if (restResource.methods.includes("list")) {
@@ -444,18 +772,19 @@ function checkWorkspaceAiFeatureBootstrap(projectDir, packageName, phpPrefix) {
444
772
  }
445
773
  function getWorkspaceEditorPluginRequiredFiles(editorPlugin) {
446
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");
447
776
  return Array.from(new Set([
448
777
  editorPlugin.file,
449
- path2.join(editorPluginDir, "Sidebar.tsx"),
778
+ surfaceFile,
450
779
  path2.join(editorPluginDir, "data.ts"),
451
780
  path2.join(editorPluginDir, "types.ts"),
452
781
  path2.join(editorPluginDir, "style.scss")
453
782
  ]));
454
783
  }
455
784
  function checkWorkspaceEditorPluginConfig(editorPlugin) {
456
- const validSlots = new Set(EDITOR_PLUGIN_SLOT_IDS);
457
- const isValidSlot = validSlots.has(editorPlugin.slot);
458
- return createDoctorCheck2(`Editor plugin config ${editorPlugin.slug}`, isValidSlot ? "pass" : "fail", isValidSlot ? `Editor plugin slot ${editorPlugin.slot} is supported` : `Unsupported editor plugin slot "${editorPlugin.slot}". Expected one of: ${EDITOR_PLUGIN_SLOT_IDS.join(", ")}`);
785
+ const normalizedSlot = resolveEditorPluginSlotAlias(editorPlugin.slot);
786
+ const isValidSlot = Boolean(normalizedSlot);
787
+ return createDoctorCheck2(`Editor plugin config ${editorPlugin.slug}`, isValidSlot ? "pass" : "fail", isValidSlot ? `Editor plugin slot ${editorPlugin.slot} is supported as ${normalizedSlot}` : `Unsupported editor plugin slot "${editorPlugin.slot}". Expected one of: ${EDITOR_PLUGIN_SLOT_IDS.join(", ")} or legacy aliases PluginSidebar, PluginDocumentSettingPanel.`);
459
788
  }
460
789
  function checkWorkspaceEditorPluginBootstrap(projectDir, packageName, phpPrefix) {
461
790
  const packageBaseName = packageName.split("/").pop() ?? packageName;
@@ -488,16 +817,114 @@ function checkWorkspaceEditorPluginIndex(projectDir, editorPlugins) {
488
817
  });
489
818
  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(", ")}`);
490
819
  }
820
+ function getWorkspaceAdminViewRequiredFiles(adminView) {
821
+ const adminViewDir = path2.join("src", "admin-views", adminView.slug);
822
+ return Array.from(new Set([
823
+ adminView.file,
824
+ 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")
830
+ ]));
831
+ }
832
+ function checkWorkspaceAdminViewConfig(adminView, inventory) {
833
+ if (adminView.source === undefined) {
834
+ return createDoctorCheck2(`Admin view config ${adminView.slug}`, "pass", "Admin view uses a replaceable local fetcher");
835
+ }
836
+ const source = adminView.source.trim();
837
+ const restSourceMatch = /^rest-resource:([a-z][a-z0-9-]*)$/u.exec(source);
838
+ const coreDataSourceMatch = /^core-data:(postType|taxonomy)\/([a-z0-9][a-z0-9_-]*)$/u.exec(source);
839
+ const restResourceSlug = restSourceMatch?.[1];
840
+ const restResource = restResourceSlug ? inventory.restResources.find((entry) => entry.slug === restResourceSlug) : undefined;
841
+ const isValid = Boolean(restResource?.methods.includes("list")) || Boolean(coreDataSourceMatch);
842
+ return createDoctorCheck2(`Admin view config ${adminView.slug}`, isValid ? "pass" : "fail", isValid ? `Admin view source ${source} is list-capable` : "Admin view source must use rest-resource:<slug> with a list-capable REST resource or core-data:<postType|taxonomy>/<name>");
843
+ }
844
+ function checkWorkspaceAdminViewBootstrap(projectDir, packageName, phpPrefix) {
845
+ 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)}`);
849
+ }
850
+ const source = fs2.readFileSync(bootstrapPath, "utf8");
851
+ const loadFunctionName = `${phpPrefix}_load_admin_views`;
852
+ const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
853
+ const hasLoaderHook = source.includes(loadHook);
854
+ const hasServerGlob = source.includes(WORKSPACE_ADMIN_VIEW_GLOB);
855
+ return createDoctorCheck2("Admin view bootstrap", hasLoaderHook && hasServerGlob ? "pass" : "fail", hasLoaderHook && hasServerGlob ? "Admin view PHP loader hook is present" : "Missing admin view PHP require glob or plugins_loaded hook");
856
+ }
857
+ function checkWorkspaceAdminViewIndex(projectDir, adminViews) {
858
+ 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)));
862
+ if (!indexRelativePath) {
863
+ return createDoctorCheck2("Admin views index", "fail", "Missing src/admin-views/index.ts or src/admin-views/index.js");
864
+ }
865
+ const indexPath = path2.join(projectDir, indexRelativePath);
866
+ const source = fs2.readFileSync(indexPath, "utf8");
867
+ const missingImports = adminViews.filter((adminView) => {
868
+ const importPattern = new RegExp(`['"\`]\\./${escapeRegex(adminView.slug)}(?:/[^'"\`]*)?['"\`]`, "u");
869
+ return !importPattern.test(source);
870
+ });
871
+ 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
+ }
873
+ function checkWorkspaceAdminViewPhp(projectDir, adminView) {
874
+ const phpPath = path2.join(projectDir, adminView.phpFile);
875
+ if (!fs2.existsSync(phpPath)) {
876
+ return createDoctorCheck2(`Admin view PHP ${adminView.slug}`, "fail", `Missing ${adminView.phpFile}`);
877
+ }
878
+ const source = fs2.readFileSync(phpPath, "utf8");
879
+ const hasAdminMenu = source.includes("add_submenu_page");
880
+ const hasAdminEnqueue = source.includes("admin_enqueue_scripts");
881
+ const hasScript = source.includes(WORKSPACE_ADMIN_VIEW_SCRIPT);
882
+ const hasAsset = source.includes(WORKSPACE_ADMIN_VIEW_ASSET);
883
+ const hasStyle = source.includes(WORKSPACE_ADMIN_VIEW_STYLE);
884
+ const hasComponentsStyleDependency = source.includes("'wp-components'");
885
+ 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
+ }
491
887
  function checkVariationEntrypoint(projectDir, blockSlug) {
492
888
  const entryPath = path2.join(projectDir, "src", "blocks", blockSlug, "index.tsx");
493
889
  if (!fs2.existsSync(entryPath)) {
494
890
  return createDoctorCheck2(`Variation entrypoint ${blockSlug}`, "fail", `Missing ${path2.relative(projectDir, entryPath)}`);
495
891
  }
496
892
  const source = fs2.readFileSync(entryPath, "utf8");
497
- const hasImport = source.includes("./variations");
498
- const hasCall = source.includes("registerWorkspaceVariations()");
893
+ const hasImport = hasUncommentedPattern(source, WORKSPACE_VARIATIONS_IMPORT_PATTERN);
894
+ const hasCall = hasExecutablePattern(source, WORKSPACE_VARIATIONS_CALL_PATTERN);
499
895
  return createDoctorCheck2(`Variation entrypoint ${blockSlug}`, hasImport && hasCall ? "pass" : "fail", hasImport && hasCall ? "Variations registration hook is present" : "Missing ./variations import or registerWorkspaceVariations() call");
500
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)}`);
901
+ }
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)}`);
911
+ }
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");
916
+ }
917
+ function checkBlockTransformConfig(workspace, transform) {
918
+ const expectedTo = `${workspace.workspace.namespace}/${transform.block}`;
919
+ const issues = [];
920
+ if (!WORKSPACE_FULL_BLOCK_NAME_PATTERN.test(transform.from)) {
921
+ issues.push("from must use full namespace/block format");
922
+ }
923
+ if (transform.to !== expectedTo) {
924
+ issues.push(`to must equal "${expectedTo}" for workspace block "${transform.block}"`);
925
+ }
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("; "));
927
+ }
501
928
  function checkMigrationWorkspaceHint(workspace, packageJson) {
502
929
  const hasMigrationScript = typeof packageJson.scripts?.["migration:doctor"] === "string";
503
930
  const migrationConfigRelativePath = path2.join("src", "migrations", "config.ts");
@@ -539,12 +966,13 @@ function getWorkspaceDoctorChecks(cwd) {
539
966
  checks.push(checkWorkspacePackageMetadata(workspace, workspacePackageJson));
540
967
  try {
541
968
  const inventory = readWorkspaceInventory(workspace.projectDir);
542
- checks.push(createDoctorCheck2("Workspace inventory", "pass", `${inventory.blocks.length} block(s), ${inventory.variations.length} variation(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)`));
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)`));
543
970
  for (const block of inventory.blocks) {
544
971
  checks.push(checkExistingFiles(workspace.projectDir, `Block ${block.slug}`, getWorkspaceBlockRequiredFiles(block)));
545
972
  checks.push(checkWorkspaceBlockMetadata(workspace.projectDir, workspace, block));
546
973
  checks.push(checkWorkspaceBlockHooks(workspace.projectDir, block.slug));
547
974
  checks.push(checkWorkspaceBlockCollectionImport(workspace.projectDir, block.slug));
975
+ checks.push(...checkWorkspaceBlockIframeCompatibility(workspace.projectDir, block.slug));
548
976
  }
549
977
  const registeredBlockSlugs = new Set(inventory.blocks.map((block) => block.slug));
550
978
  const variationTargetBlocks = new Set;
@@ -559,6 +987,37 @@ function getWorkspaceDoctorChecks(cwd) {
559
987
  for (const blockSlug of variationTargetBlocks) {
560
988
  checks.push(checkVariationEntrypoint(workspace.projectDir, blockSlug));
561
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
+ }
562
1021
  const shouldCheckPatternBootstrap = inventory.patterns.length > 0 || fs2.existsSync(path2.join(workspace.projectDir, "src", "patterns"));
563
1022
  if (shouldCheckPatternBootstrap) {
564
1023
  checks.push(checkWorkspacePatternBootstrap(workspace.projectDir, workspace.packageName));
@@ -575,6 +1034,10 @@ function getWorkspaceDoctorChecks(cwd) {
575
1034
  bindingSource.serverFile,
576
1035
  bindingSource.editorFile
577
1036
  ]));
1037
+ const bindingTargetCheck = checkWorkspaceBindingTarget(workspace.projectDir, workspace, registeredBlockSlugs, bindingSource);
1038
+ if (bindingTargetCheck) {
1039
+ checks.push(bindingTargetCheck);
1040
+ }
578
1041
  }
579
1042
  if (inventory.restResources.length > 0) {
580
1043
  checks.push(checkWorkspaceRestResourceBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
@@ -606,6 +1069,15 @@ function getWorkspaceDoctorChecks(cwd) {
606
1069
  checks.push(checkExistingFiles(workspace.projectDir, `Editor plugin ${editorPlugin.slug}`, getWorkspaceEditorPluginRequiredFiles(editorPlugin)));
607
1070
  checks.push(checkWorkspaceEditorPluginConfig(editorPlugin));
608
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
+ }
609
1081
  const migrationWorkspaceCheck = checkMigrationWorkspaceHint(workspace, workspacePackageJson);
610
1082
  if (migrationWorkspaceCheck) {
611
1083
  checks.push(migrationWorkspaceCheck);
@@ -636,6 +1108,7 @@ async function runDoctor(cwd, options = {}) {
636
1108
  const failureDetailLines = getDoctorFailureDetailLines(checks);
637
1109
  if (failureDetailLines.length > 0) {
638
1110
  throw createCliCommandError({
1111
+ code: CLI_DIAGNOSTIC_CODES.DOCTOR_CHECK_FAILED,
639
1112
  command: "doctor",
640
1113
  detailLines: failureDetailLines,
641
1114
  summary: "One or more doctor checks failed."
@@ -648,4 +1121,4 @@ export {
648
1121
  getDoctorChecks
649
1122
  };
650
1123
 
651
- //# debugId=DA7EC2F06C52A22464756E2164756E21
1124
+ //# debugId=0A56FB55109F0ECC64756E2164756E21