@wp-typia/project-tools 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.
Files changed (62) hide show
  1. package/dist/runtime/built-in-block-code-templates/interactivity.d.ts +1 -1
  2. package/dist/runtime/built-in-block-code-templates/interactivity.js +4 -2
  3. package/dist/runtime/cli-add-block-json.d.ts +31 -0
  4. package/dist/runtime/cli-add-block-json.js +65 -0
  5. package/dist/runtime/cli-add-collision.d.ts +129 -0
  6. package/dist/runtime/cli-add-collision.js +293 -0
  7. package/dist/runtime/cli-add-filesystem.d.ts +29 -0
  8. package/dist/runtime/cli-add-filesystem.js +77 -0
  9. package/dist/runtime/cli-add-help.d.ts +4 -0
  10. package/dist/runtime/cli-add-help.js +41 -0
  11. package/dist/runtime/cli-add-shared.d.ts +6 -255
  12. package/dist/runtime/cli-add-shared.js +6 -391
  13. package/dist/runtime/cli-add-types.d.ts +247 -0
  14. package/dist/runtime/cli-add-types.js +64 -0
  15. package/dist/runtime/cli-add-validation.d.ts +87 -0
  16. package/dist/runtime/cli-add-validation.js +147 -0
  17. package/dist/runtime/cli-add-workspace-ability-scaffold.d.ts +5 -0
  18. package/dist/runtime/cli-add-workspace-ability-scaffold.js +366 -0
  19. package/dist/runtime/cli-add-workspace-ability-templates.d.ts +34 -0
  20. package/dist/runtime/cli-add-workspace-ability-templates.js +500 -0
  21. package/dist/runtime/cli-add-workspace-ability-types.d.ts +27 -0
  22. package/dist/runtime/cli-add-workspace-ability-types.js +14 -0
  23. package/dist/runtime/cli-add-workspace-ability.js +12 -852
  24. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +35 -61
  25. package/dist/runtime/cli-add-workspace-ai-scaffold.d.ts +21 -0
  26. package/dist/runtime/cli-add-workspace-ai-scaffold.js +87 -0
  27. package/dist/runtime/cli-add-workspace-ai-templates.d.ts +4 -0
  28. package/dist/runtime/cli-add-workspace-ai-templates.js +607 -0
  29. package/dist/runtime/cli-add-workspace-ai.js +15 -688
  30. package/dist/runtime/cli-add-workspace-assets.js +7 -4
  31. package/dist/runtime/cli-add-workspace-mutation.d.ts +30 -0
  32. package/dist/runtime/cli-add-workspace-mutation.js +60 -0
  33. package/dist/runtime/cli-add-workspace.js +2 -98
  34. package/dist/runtime/cli-add.d.ts +2 -2
  35. package/dist/runtime/cli-add.js +2 -2
  36. package/dist/runtime/cli-doctor-workspace-bindings.d.ts +11 -0
  37. package/dist/runtime/cli-doctor-workspace-bindings.js +134 -0
  38. package/dist/runtime/cli-doctor-workspace-blocks.d.ts +11 -0
  39. package/dist/runtime/cli-doctor-workspace-blocks.js +439 -0
  40. package/dist/runtime/cli-doctor-workspace-features.d.ts +11 -0
  41. package/dist/runtime/cli-doctor-workspace-features.js +383 -0
  42. package/dist/runtime/cli-doctor-workspace-package.d.ts +18 -0
  43. package/dist/runtime/cli-doctor-workspace-package.js +59 -0
  44. package/dist/runtime/cli-doctor-workspace-shared.d.ts +69 -0
  45. package/dist/runtime/cli-doctor-workspace-shared.js +87 -0
  46. package/dist/runtime/cli-doctor-workspace.js +25 -1062
  47. package/dist/runtime/index.d.ts +2 -0
  48. package/dist/runtime/index.js +1 -0
  49. package/dist/runtime/migration-utils.d.ts +2 -1
  50. package/dist/runtime/migration-utils.js +3 -11
  51. package/dist/runtime/package-managers.d.ts +19 -0
  52. package/dist/runtime/package-managers.js +62 -0
  53. package/dist/runtime/template-source-cache.d.ts +59 -0
  54. package/dist/runtime/template-source-cache.js +160 -0
  55. package/dist/runtime/ts-source-masking.d.ts +28 -0
  56. package/dist/runtime/ts-source-masking.js +104 -0
  57. package/dist/runtime/typia-llm.d.ts +9 -1
  58. package/dist/runtime/typia-llm.js +20 -5
  59. package/dist/runtime/workspace-inventory.js +116 -59
  60. package/dist/runtime/workspace-project.d.ts +1 -1
  61. package/dist/runtime/workspace-project.js +2 -10
  62. package/package.json +4 -4
@@ -8,6 +8,7 @@ import { readWorkspaceInventory, appendWorkspaceInventoryEntries, } from "./work
8
8
  import { toPascalCase, toTitleCase } from "./string-case.js";
9
9
  import { findPhpFunctionRange, hasPhpFunctionDefinition, quotePhpString, replacePhpFunctionDefinition, } from "./php-utils.js";
10
10
  import { assertBindingSourceDoesNotExist, assertEditorPluginDoesNotExist, assertPatternDoesNotExist, assertValidEditorPluginSlot, assertValidGeneratedSlug, getWorkspaceBootstrapPath, normalizeBlockSlug, patchFile, quoteTsString, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
11
+ import { normalizeOptionalCliString } from "./cli-validation.js";
11
12
  const PATTERN_BOOTSTRAP_CATEGORY = "register_block_pattern_category";
12
13
  const BINDING_SOURCE_SERVER_GLOB = "/src/bindings/*/server.php";
13
14
  const BINDING_SOURCE_EDITOR_SCRIPT = "build/bindings/index.js";
@@ -225,8 +226,10 @@ registerBlockBindingsSource( {
225
226
  `;
226
227
  }
227
228
  function resolveBindingTarget(options, namespace) {
228
- const hasBlock = options.blockName !== undefined && options.blockName.trim().length > 0;
229
- const hasAttribute = options.attributeName !== undefined && options.attributeName.trim().length > 0;
229
+ const blockName = normalizeOptionalCliString(options.blockName);
230
+ const attributeName = normalizeOptionalCliString(options.attributeName);
231
+ const hasBlock = blockName !== undefined;
232
+ const hasAttribute = attributeName !== undefined;
230
233
  if (!hasBlock && !hasAttribute) {
231
234
  return undefined;
232
235
  }
@@ -234,8 +237,8 @@ function resolveBindingTarget(options, namespace) {
234
237
  throw new Error("`wp-typia add binding-source` requires --block and --attribute to be provided together.");
235
238
  }
236
239
  return {
237
- attributeName: assertValidBindingAttributeName(options.attributeName ?? ""),
238
- blockSlug: resolveBindingTargetBlockSlug(options.blockName ?? "", namespace),
240
+ attributeName: assertValidBindingAttributeName(attributeName ?? ""),
241
+ blockSlug: resolveBindingTargetBlockSlug(blockName ?? "", namespace),
239
242
  };
240
243
  }
241
244
  function formatBindingAttributeTypeMember(attributeName) {
@@ -0,0 +1,30 @@
1
+ export interface WorkspaceMutationPlan<TResult> {
2
+ /** Files to capture before the mutation starts. Missing files are restored as absent. */
3
+ filePaths: string[];
4
+ /** Snapshot directories created by the mutation, usually migration fixtures. */
5
+ snapshotDirs?: string[];
6
+ /** Created files or directories to remove if the mutation fails. */
7
+ targetPaths?: string[];
8
+ /** Mutating work to execute after the snapshot is captured. */
9
+ run: () => Promise<TResult>;
10
+ }
11
+ /**
12
+ * Error thrown when the mutation and its rollback both fail.
13
+ */
14
+ export declare class WorkspaceMutationRollbackError extends Error {
15
+ readonly mutationError: unknown;
16
+ readonly rollbackError: unknown;
17
+ constructor(mutationError: unknown, rollbackError: unknown);
18
+ }
19
+ /**
20
+ * Execute a workspace add mutation with rollback on any failure.
21
+ */
22
+ export declare function executeWorkspaceMutationPlan<TResult>({ filePaths, run, snapshotDirs, targetPaths, }: WorkspaceMutationPlan<TResult>): Promise<TResult>;
23
+ /**
24
+ * Insert a PHP snippet before the workspace textdomain hook or closing tag.
25
+ */
26
+ export declare function insertPhpSnippetBeforeWorkspaceAnchors(source: string, snippet: string): string;
27
+ /**
28
+ * Append a PHP snippet before the closing tag when one is present.
29
+ */
30
+ export declare function appendPhpSnippetBeforeClosingTag(source: string, snippet: string): string;
@@ -0,0 +1,60 @@
1
+ import { rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
2
+ const DEFAULT_PHP_SNIPPET_INSERTION_ANCHORS = [
3
+ /add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
4
+ /\?>\s*$/u,
5
+ ];
6
+ /**
7
+ * Error thrown when the mutation and its rollback both fail.
8
+ */
9
+ export class WorkspaceMutationRollbackError extends Error {
10
+ constructor(mutationError, rollbackError) {
11
+ super("Workspace mutation failed and rollback also failed.");
12
+ this.name = "WorkspaceMutationRollbackError";
13
+ this.mutationError = mutationError;
14
+ this.rollbackError = rollbackError;
15
+ }
16
+ }
17
+ /**
18
+ * Execute a workspace add mutation with rollback on any failure.
19
+ */
20
+ export async function executeWorkspaceMutationPlan({ filePaths, run, snapshotDirs = [], targetPaths = [], }) {
21
+ const mutationSnapshot = {
22
+ fileSources: await snapshotWorkspaceFiles(filePaths),
23
+ snapshotDirs: [...snapshotDirs],
24
+ targetPaths: [...targetPaths],
25
+ };
26
+ try {
27
+ return await run();
28
+ }
29
+ catch (error) {
30
+ try {
31
+ await rollbackWorkspaceMutation(mutationSnapshot);
32
+ }
33
+ catch (rollbackError) {
34
+ throw new WorkspaceMutationRollbackError(error, rollbackError);
35
+ }
36
+ throw error;
37
+ }
38
+ }
39
+ /**
40
+ * Insert a PHP snippet before the workspace textdomain hook or closing tag.
41
+ */
42
+ export function insertPhpSnippetBeforeWorkspaceAnchors(source, snippet) {
43
+ for (const anchor of DEFAULT_PHP_SNIPPET_INSERTION_ANCHORS) {
44
+ const candidate = source.replace(anchor, (match) => `${snippet}\n${match}`);
45
+ if (candidate !== source) {
46
+ return candidate;
47
+ }
48
+ }
49
+ return `${source.trimEnd()}\n${snippet}\n`;
50
+ }
51
+ /**
52
+ * Append a PHP snippet before the closing tag when one is present.
53
+ */
54
+ export function appendPhpSnippetBeforeClosingTag(source, snippet) {
55
+ const closingTagPattern = /\?>\s*$/u;
56
+ if (closingTagPattern.test(source)) {
57
+ return source.replace(closingTagPattern, `${snippet}\n?>`);
58
+ }
59
+ return `${source.trimEnd()}\n${snippet}\n`;
60
+ }
@@ -4,7 +4,8 @@ import path from "node:path";
4
4
  import { resolveWorkspaceProject } from "./workspace-project.js";
5
5
  import { appendWorkspaceInventoryEntries, readWorkspaceInventory } from "./workspace-inventory.js";
6
6
  import { toKebabCase, toSnakeCase, toTitleCase } from "./string-case.js";
7
- import { assertValidGeneratedSlug, assertValidHookAnchor, assertValidHookedBlockPosition, assertVariationDoesNotExist, getMutableBlockHooks, normalizeBlockSlug, patchFile, quoteTsString, readWorkspaceBlockJson, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
7
+ import { assertBlockStyleDoesNotExist, assertBlockTransformDoesNotExist, assertValidGeneratedSlug, assertValidHookAnchor, assertValidHookedBlockPosition, assertVariationDoesNotExist, getMutableBlockHooks, normalizeBlockSlug, patchFile, quoteTsString, readWorkspaceBlockJson, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
8
+ import { findExecutablePatternMatch, hasExecutablePattern, hasUncommentedPattern, maskTypeScriptCommentsAndLiterals, } from "./ts-source-masking.js";
8
9
  const VARIATIONS_IMPORT_LINE = "import { registerWorkspaceVariations } from './variations';";
9
10
  const VARIATIONS_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceVariations\s*\}\s*from\s*["']\.\/variations["']\s*;?\s*$/mu;
10
11
  const VARIATIONS_CALL_LINE = "registerWorkspaceVariations();";
@@ -19,85 +20,6 @@ const BLOCK_TRANSFORMS_CALL_LINE = "applyWorkspaceBlockTransforms(registration.s
19
20
  const BLOCK_TRANSFORMS_CALL_PATTERN = /applyWorkspaceBlockTransforms\s*\(\s*registration\s*\.\s*settings\s*\)\s*;?/u;
20
21
  const SCAFFOLD_REGISTRATION_SETTINGS_CALL_PATTERN = /registerScaffoldBlockType\s*\(\s*registration\s*\.\s*name\s*,\s*registration\s*\.\s*settings\s*\)\s*;?/u;
21
22
  const FULL_BLOCK_NAME_PATTERN = /^[a-z0-9-]+\/[a-z0-9-]+$/u;
22
- function maskSourceSegment(segment) {
23
- return segment.replace(/[^\n\r]/gu, " ");
24
- }
25
- function maskTypeScriptComments(source) {
26
- return source
27
- .replace(/\/\*[\s\S]*?\*\//gu, maskSourceSegment)
28
- .replace(/\/\/[^\n\r]*/gu, maskSourceSegment);
29
- }
30
- // Preserve source offsets so executable-code match indexes still map to the original file.
31
- function maskTypeScriptCommentsAndLiterals(source) {
32
- let maskedSource = "";
33
- let index = 0;
34
- while (index < source.length) {
35
- const current = source[index];
36
- const next = source[index + 1];
37
- if (current === "/" && next === "/") {
38
- const start = index;
39
- index += 2;
40
- while (index < source.length &&
41
- source[index] !== "\n" &&
42
- source[index] !== "\r") {
43
- index += 1;
44
- }
45
- maskedSource += maskSourceSegment(source.slice(start, index));
46
- continue;
47
- }
48
- if (current === "/" && next === "*") {
49
- const start = index;
50
- index += 2;
51
- while (index < source.length &&
52
- !(source[index] === "*" && source[index + 1] === "/")) {
53
- index += 1;
54
- }
55
- index = Math.min(index + 2, source.length);
56
- maskedSource += maskSourceSegment(source.slice(start, index));
57
- continue;
58
- }
59
- if (current === "'" || current === '"' || current === "`") {
60
- const start = index;
61
- const quote = current;
62
- index += 1;
63
- while (index < source.length) {
64
- const char = source[index];
65
- if (char === "\\") {
66
- index += 2;
67
- continue;
68
- }
69
- index += 1;
70
- if (char === quote) {
71
- break;
72
- }
73
- }
74
- maskedSource += maskSourceSegment(source.slice(start, index));
75
- continue;
76
- }
77
- maskedSource += current;
78
- index += 1;
79
- }
80
- return maskedSource;
81
- }
82
- function hasExecutablePattern(source, pattern) {
83
- return pattern.test(maskTypeScriptCommentsAndLiterals(source));
84
- }
85
- function hasUncommentedPattern(source, pattern) {
86
- return pattern.test(maskTypeScriptComments(source));
87
- }
88
- function findExecutablePatternMatch(source, patterns) {
89
- const maskedSource = maskTypeScriptCommentsAndLiterals(source);
90
- for (const pattern of patterns) {
91
- const match = pattern.exec(maskedSource);
92
- if (match && match.index !== undefined) {
93
- return {
94
- end: match.index + match[0].length,
95
- start: match.index,
96
- };
97
- }
98
- }
99
- return undefined;
100
- }
101
23
  function isIdentifierBoundary(source, index) {
102
24
  if (index < 0 || index >= source.length) {
103
25
  return true;
@@ -488,24 +410,6 @@ async function writeBlockTransformRegistry(projectDir, blockSlug, transformSlug)
488
410
  const nextTransformSlugs = Array.from(new Set([...existingTransformSlugs, transformSlug])).sort();
489
411
  await fsp.writeFile(transformsIndexPath, buildBlockTransformIndexSource(nextTransformSlugs), "utf8");
490
412
  }
491
- function assertBlockStyleDoesNotExist(projectDir, blockSlug, styleSlug, inventory) {
492
- const stylePath = path.join(projectDir, "src", "blocks", blockSlug, "styles", `${styleSlug}.ts`);
493
- if (fs.existsSync(stylePath)) {
494
- throw new Error(`A block style already exists at ${path.relative(projectDir, stylePath)}. Choose a different name.`);
495
- }
496
- if (inventory.blockStyles.some((entry) => entry.block === blockSlug && entry.slug === styleSlug)) {
497
- throw new Error(`A block style inventory entry already exists for ${blockSlug}/${styleSlug}. Choose a different name.`);
498
- }
499
- }
500
- function assertBlockTransformDoesNotExist(projectDir, blockSlug, transformSlug, inventory) {
501
- const transformPath = path.join(projectDir, "src", "blocks", blockSlug, "transforms", `${transformSlug}.ts`);
502
- if (fs.existsSync(transformPath)) {
503
- throw new Error(`A block transform already exists at ${path.relative(projectDir, transformPath)}. Choose a different name.`);
504
- }
505
- if (inventory.blockTransforms.some((entry) => entry.block === blockSlug && entry.slug === transformSlug)) {
506
- throw new Error(`A block transform inventory entry already exists for ${blockSlug}/${transformSlug}. Choose a different name.`);
507
- }
508
- }
509
413
  function assertFullBlockName(blockName, flagName) {
510
414
  const trimmed = blockName.trim();
511
415
  if (!trimmed) {
@@ -3,11 +3,11 @@
3
3
  *
4
4
  * The canonical CLI surface stays stable here while the implementation lives
5
5
  * in focused internal modules:
6
- * - `cli-add-shared` for shared validation/help/rollback helpers
6
+ * - `cli-add-shared` as a compatibility barrel around focused add helpers
7
7
  * - `cli-add-block` for built-in block scaffolding
8
8
  * - `cli-add-workspace` for workspace mutation commands
9
9
  */
10
- export { ADD_BLOCK_TEMPLATE_IDS, ADD_KIND_IDS, EDITOR_PLUGIN_SLOT_IDS, formatAddHelpText, } from "./cli-add-shared.js";
10
+ export { ADD_BLOCK_TEMPLATE_IDS, ADD_KIND_IDS, EDITOR_PLUGIN_SLOT_IDS, formatAddHelpText, isAddBlockTemplateId, } from "./cli-add-shared.js";
11
11
  export type { AddBlockTemplateId, AddKindId, EditorPluginSlotId, } from "./cli-add-shared.js";
12
12
  export { runAddBlockCommand, seedWorkspaceMigrationProject, } from "./cli-add-block.js";
13
13
  export { runAddAdminViewCommand, runAddAbilityCommand, runAddAiFeatureCommand, runAddBindingSourceCommand, runAddBlockStyleCommand, runAddBlockTransformCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runAddRestResourceCommand, runAddVariationCommand, } from "./cli-add-workspace.js";
@@ -3,11 +3,11 @@
3
3
  *
4
4
  * The canonical CLI surface stays stable here while the implementation lives
5
5
  * in focused internal modules:
6
- * - `cli-add-shared` for shared validation/help/rollback helpers
6
+ * - `cli-add-shared` as a compatibility barrel around focused add helpers
7
7
  * - `cli-add-block` for built-in block scaffolding
8
8
  * - `cli-add-workspace` for workspace mutation commands
9
9
  */
10
- export { ADD_BLOCK_TEMPLATE_IDS, ADD_KIND_IDS, EDITOR_PLUGIN_SLOT_IDS, formatAddHelpText, } from "./cli-add-shared.js";
10
+ export { ADD_BLOCK_TEMPLATE_IDS, ADD_KIND_IDS, EDITOR_PLUGIN_SLOT_IDS, formatAddHelpText, isAddBlockTemplateId, } from "./cli-add-shared.js";
11
11
  export { runAddBlockCommand, seedWorkspaceMigrationProject, } from "./cli-add-block.js";
12
12
  export { runAddAdminViewCommand, runAddAbilityCommand, runAddAiFeatureCommand, runAddBindingSourceCommand, runAddBlockStyleCommand, runAddBlockTransformCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runAddRestResourceCommand, runAddVariationCommand, } from "./cli-add-workspace.js";
13
13
  export { getWorkspaceBlockSelectOptions } from "./workspace-inventory.js";
@@ -0,0 +1,11 @@
1
+ import type { DoctorCheck } from "./cli-doctor.js";
2
+ import type { WorkspaceInventory } from "./workspace-inventory.js";
3
+ import type { WorkspaceProject } from "./workspace-project.js";
4
+ /**
5
+ * Collect workspace doctor checks for extracted binding-source diagnostics.
6
+ *
7
+ * @param workspace Resolved workspace metadata and filesystem paths.
8
+ * @param inventory Parsed workspace inventory from `scripts/block-config.ts`.
9
+ * @returns Ordered `DoctorCheck[]` rows for binding bootstrap, index, and target checks.
10
+ */
11
+ export declare function getWorkspaceBindingDoctorChecks(workspace: WorkspaceProject, inventory: WorkspaceInventory): DoctorCheck[];
@@ -0,0 +1,134 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { parseScaffoldBlockMetadata } from "@wp-typia/block-runtime/blocks";
4
+ import { checkExistingFiles, createDoctorCheck, WORKSPACE_BINDING_EDITOR_ASSET, WORKSPACE_BINDING_EDITOR_SCRIPT, WORKSPACE_BINDING_SERVER_GLOB, } from "./cli-doctor-workspace-shared.js";
5
+ import { escapeRegex } from "./php-utils.js";
6
+ function checkWorkspaceBindingBootstrap(projectDir, packageName) {
7
+ const packageBaseName = packageName.split("/").pop() ?? packageName;
8
+ const bootstrapPath = path.join(projectDir, `${packageBaseName}.php`);
9
+ if (!fs.existsSync(bootstrapPath)) {
10
+ return createDoctorCheck("Binding bootstrap", "fail", `Missing ${path.basename(bootstrapPath)}`);
11
+ }
12
+ const source = fs.readFileSync(bootstrapPath, "utf8");
13
+ const hasServerGlob = source.includes(WORKSPACE_BINDING_SERVER_GLOB);
14
+ const hasEditorEnqueueHook = source.includes("enqueue_block_editor_assets");
15
+ const hasEditorScript = source.includes(WORKSPACE_BINDING_EDITOR_SCRIPT);
16
+ const hasEditorAsset = source.includes(WORKSPACE_BINDING_EDITOR_ASSET);
17
+ return createDoctorCheck("Binding bootstrap", hasServerGlob && hasEditorEnqueueHook && hasEditorScript && hasEditorAsset ? "pass" : "fail", hasServerGlob && hasEditorEnqueueHook && hasEditorScript && hasEditorAsset
18
+ ? "Binding source PHP and editor bootstrap hooks are present"
19
+ : "Missing binding source PHP require glob or editor enqueue hook");
20
+ }
21
+ function checkWorkspaceBindingSourcesIndex(projectDir, bindingSources) {
22
+ const indexRelativePath = [path.join("src", "bindings", "index.ts"), path.join("src", "bindings", "index.js")].find((relativePath) => fs.existsSync(path.join(projectDir, relativePath)));
23
+ if (!indexRelativePath) {
24
+ return createDoctorCheck("Binding sources index", "fail", "Missing src/bindings/index.ts or src/bindings/index.js");
25
+ }
26
+ const indexPath = path.join(projectDir, indexRelativePath);
27
+ const source = fs.readFileSync(indexPath, "utf8");
28
+ const missingImports = bindingSources.filter((bindingSource) => !source.includes(`./${bindingSource.slug}/editor`));
29
+ return createDoctorCheck("Binding sources index", missingImports.length === 0 ? "pass" : "fail", missingImports.length === 0
30
+ ? "Binding source editor registrations are aggregated"
31
+ : `Missing editor imports for: ${missingImports.map((entry) => entry.slug).join(", ")}`);
32
+ }
33
+ function checkWorkspaceBindingTarget(projectDir, workspace, registeredBlockSlugs, bindingSource) {
34
+ const hasBlock = bindingSource.block !== undefined;
35
+ const hasAttribute = bindingSource.attribute !== undefined;
36
+ if (!hasBlock && !hasAttribute) {
37
+ return undefined;
38
+ }
39
+ if (!bindingSource.block || !bindingSource.attribute) {
40
+ return createDoctorCheck(`Binding target ${bindingSource.slug}`, "fail", "Binding target entries must include both block and attribute.");
41
+ }
42
+ if (!registeredBlockSlugs.has(bindingSource.block)) {
43
+ return createDoctorCheck(`Binding target ${bindingSource.slug}`, "fail", `Binding target references unknown block "${bindingSource.block}".`);
44
+ }
45
+ const blockJsonRelativePath = path.join("src", "blocks", bindingSource.block, "block.json");
46
+ const blockJsonPath = path.join(projectDir, blockJsonRelativePath);
47
+ const issues = [];
48
+ try {
49
+ const blockJson = parseScaffoldBlockMetadata(JSON.parse(fs.readFileSync(blockJsonPath, "utf8")));
50
+ const attributes = blockJson.attributes;
51
+ if (!attributes || typeof attributes !== "object" || Array.isArray(attributes)) {
52
+ issues.push(`${blockJsonRelativePath} must define an attributes object`);
53
+ }
54
+ else {
55
+ const attributeConfig = attributes[bindingSource.attribute];
56
+ if (!attributeConfig ||
57
+ typeof attributeConfig !== "object" ||
58
+ Array.isArray(attributeConfig)) {
59
+ issues.push(`${blockJsonRelativePath} must declare attribute "${bindingSource.attribute}"`);
60
+ }
61
+ }
62
+ }
63
+ catch (error) {
64
+ issues.push(error instanceof Error
65
+ ? `Unable to read ${blockJsonRelativePath}: ${error.message}`
66
+ : `Unable to read ${blockJsonRelativePath}.`);
67
+ }
68
+ const serverPath = path.join(projectDir, bindingSource.serverFile);
69
+ if (fs.existsSync(serverPath)) {
70
+ const serverSource = fs.readFileSync(serverPath, "utf8");
71
+ const supportedAttributesFilter = `block_bindings_supported_attributes_${workspace.workspace.namespace}/${bindingSource.block}`;
72
+ if (!serverSource.includes(supportedAttributesFilter)) {
73
+ issues.push(`${bindingSource.serverFile} must register ${supportedAttributesFilter}`);
74
+ }
75
+ if (!new RegExp(`(['"])${escapeRegex(bindingSource.attribute)}\\1`, "u").test(serverSource)) {
76
+ issues.push(`${bindingSource.serverFile} must expose attribute "${bindingSource.attribute}"`);
77
+ }
78
+ }
79
+ else {
80
+ issues.push(`Missing ${bindingSource.serverFile}`);
81
+ }
82
+ const editorPath = path.join(projectDir, bindingSource.editorFile);
83
+ if (fs.existsSync(editorPath)) {
84
+ const editorSource = fs.readFileSync(editorPath, "utf8");
85
+ const blockName = `${workspace.workspace.namespace}/${bindingSource.block}`;
86
+ const bindingSourceTargetMatch = editorSource.match(/export\s+const\s+BINDING_SOURCE_TARGET\s*=\s*\{([\s\S]*?)\}\s+as\s+const\s*;/u);
87
+ if (!bindingSourceTargetMatch) {
88
+ issues.push(`${bindingSource.editorFile} must export BINDING_SOURCE_TARGET`);
89
+ }
90
+ else {
91
+ const targetSource = bindingSourceTargetMatch[1] ?? "";
92
+ const attributePattern = new RegExp(`\\battribute\\s*:\\s*["']${escapeRegex(bindingSource.attribute)}["']`, "u");
93
+ const blockPattern = new RegExp(`\\bblock\\s*:\\s*["']${escapeRegex(blockName)}["']`, "u");
94
+ if (!attributePattern.test(targetSource)) {
95
+ issues.push(`${bindingSource.editorFile} must document target attribute "${bindingSource.attribute}"`);
96
+ }
97
+ if (!blockPattern.test(targetSource)) {
98
+ issues.push(`${bindingSource.editorFile} must document target block "${blockName}"`);
99
+ }
100
+ }
101
+ }
102
+ else {
103
+ issues.push(`Missing ${bindingSource.editorFile}`);
104
+ }
105
+ return createDoctorCheck(`Binding target ${bindingSource.slug}`, issues.length === 0 ? "pass" : "fail", issues.length === 0
106
+ ? `${bindingSource.block}.${bindingSource.attribute} is declared and supported`
107
+ : issues.join("; "));
108
+ }
109
+ /**
110
+ * Collect workspace doctor checks for extracted binding-source diagnostics.
111
+ *
112
+ * @param workspace Resolved workspace metadata and filesystem paths.
113
+ * @param inventory Parsed workspace inventory from `scripts/block-config.ts`.
114
+ * @returns Ordered `DoctorCheck[]` rows for binding bootstrap, index, and target checks.
115
+ */
116
+ export function getWorkspaceBindingDoctorChecks(workspace, inventory) {
117
+ const checks = [];
118
+ if (inventory.bindingSources.length > 0) {
119
+ checks.push(checkWorkspaceBindingBootstrap(workspace.projectDir, workspace.packageName));
120
+ checks.push(checkWorkspaceBindingSourcesIndex(workspace.projectDir, inventory.bindingSources));
121
+ }
122
+ const registeredBlockSlugs = new Set(inventory.blocks.map((block) => block.slug));
123
+ for (const bindingSource of inventory.bindingSources) {
124
+ checks.push(checkExistingFiles(workspace.projectDir, `Binding source ${bindingSource.slug}`, [
125
+ bindingSource.serverFile,
126
+ bindingSource.editorFile,
127
+ ]));
128
+ const bindingTargetCheck = checkWorkspaceBindingTarget(workspace.projectDir, workspace, registeredBlockSlugs, bindingSource);
129
+ if (bindingTargetCheck) {
130
+ checks.push(bindingTargetCheck);
131
+ }
132
+ }
133
+ return checks;
134
+ }
@@ -0,0 +1,11 @@
1
+ import type { DoctorCheck } from "./cli-doctor.js";
2
+ import type { WorkspaceInventory } from "./workspace-inventory.js";
3
+ import type { WorkspaceProject } from "./workspace-project.js";
4
+ /**
5
+ * Collect block-, variation-, transform-, and pattern-related workspace doctor checks.
6
+ *
7
+ * @param workspace Resolved workspace metadata and filesystem paths.
8
+ * @param inventory Parsed workspace inventory from `scripts/block-config.ts`.
9
+ * @returns Ordered `DoctorCheck[]` rows for extracted block diagnostics.
10
+ */
11
+ export declare function getWorkspaceBlockDoctorChecks(workspace: WorkspaceProject, inventory: WorkspaceInventory): DoctorCheck[];