@wp-typia/project-tools 0.23.1 → 0.24.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.
Files changed (152) hide show
  1. package/dist/runtime/built-in-block-non-ts-basic-artifacts.d.ts +9 -0
  2. package/dist/runtime/built-in-block-non-ts-basic-artifacts.js +84 -0
  3. package/dist/runtime/built-in-block-non-ts-compound-artifacts.d.ts +9 -0
  4. package/dist/runtime/built-in-block-non-ts-compound-artifacts.js +36 -0
  5. package/dist/runtime/built-in-block-non-ts-compound-templates.d.ts +23 -0
  6. package/dist/runtime/built-in-block-non-ts-compound-templates.js +453 -0
  7. package/dist/runtime/built-in-block-non-ts-family-artifacts.d.ts +8 -26
  8. package/dist/runtime/built-in-block-non-ts-family-artifacts.js +8 -1034
  9. package/dist/runtime/built-in-block-non-ts-interactivity-artifacts.d.ts +9 -0
  10. package/dist/runtime/built-in-block-non-ts-interactivity-artifacts.js +83 -0
  11. package/dist/runtime/built-in-block-non-ts-persistence-artifacts.d.ts +9 -0
  12. package/dist/runtime/built-in-block-non-ts-persistence-artifacts.js +33 -0
  13. package/dist/runtime/built-in-block-non-ts-persistence-templates.d.ts +23 -0
  14. package/dist/runtime/built-in-block-non-ts-persistence-templates.js +395 -0
  15. package/dist/runtime/cli-add-collision.js +8 -0
  16. package/dist/runtime/cli-add-help.js +10 -7
  17. package/dist/runtime/cli-add-kind-ids.d.ts +1 -1
  18. package/dist/runtime/cli-add-kind-ids.js +1 -0
  19. package/dist/runtime/cli-add-types.d.ts +28 -1
  20. package/dist/runtime/cli-add-types.js +2 -0
  21. package/dist/runtime/cli-add-workspace-ability-anchors.d.ts +24 -0
  22. package/dist/runtime/cli-add-workspace-ability-anchors.js +294 -0
  23. package/dist/runtime/cli-add-workspace-ability-registry.d.ts +10 -0
  24. package/dist/runtime/cli-add-workspace-ability-registry.js +51 -0
  25. package/dist/runtime/cli-add-workspace-ability-scaffold.d.ts +1 -1
  26. package/dist/runtime/cli-add-workspace-ability-scaffold.js +5 -311
  27. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +1 -1
  28. package/dist/runtime/cli-add-workspace-ai-anchors.d.ts +4 -4
  29. package/dist/runtime/cli-add-workspace-ai-anchors.js +4 -232
  30. package/dist/runtime/cli-add-workspace-ai-scaffold.js +4 -2
  31. package/dist/runtime/cli-add-workspace-ai-source-emitters.d.ts +1 -4
  32. package/dist/runtime/cli-add-workspace-ai-source-emitters.js +1 -145
  33. package/dist/runtime/cli-add-workspace-ai-sync-rest-anchors.d.ts +5 -0
  34. package/dist/runtime/cli-add-workspace-ai-sync-rest-anchors.js +236 -0
  35. package/dist/runtime/cli-add-workspace-ai-sync-script-source.d.ts +4 -0
  36. package/dist/runtime/cli-add-workspace-ai-sync-script-source.js +145 -0
  37. package/dist/runtime/cli-add-workspace-assets.d.ts +6 -63
  38. package/dist/runtime/cli-add-workspace-assets.js +6 -950
  39. package/dist/runtime/cli-add-workspace-binding-source-anchors.d.ts +23 -0
  40. package/dist/runtime/cli-add-workspace-binding-source-anchors.js +112 -0
  41. package/dist/runtime/cli-add-workspace-binding-source-source-emitters.d.ts +33 -0
  42. package/dist/runtime/cli-add-workspace-binding-source-source-emitters.js +436 -0
  43. package/dist/runtime/cli-add-workspace-binding-source-types.d.ts +20 -0
  44. package/dist/runtime/cli-add-workspace-binding-source-types.js +1 -0
  45. package/dist/runtime/cli-add-workspace-binding-source.d.ts +40 -0
  46. package/dist/runtime/cli-add-workspace-binding-source.js +275 -0
  47. package/dist/runtime/cli-add-workspace-block-style.d.ts +22 -0
  48. package/dist/runtime/cli-add-workspace-block-style.js +148 -0
  49. package/dist/runtime/cli-add-workspace-block-transform.d.ts +32 -0
  50. package/dist/runtime/cli-add-workspace-block-transform.js +197 -0
  51. package/dist/runtime/cli-add-workspace-contract.js +1 -1
  52. package/dist/runtime/cli-add-workspace-core-variation.d.ts +20 -0
  53. package/dist/runtime/cli-add-workspace-core-variation.js +322 -0
  54. package/dist/runtime/cli-add-workspace-editor-plugin-anchors.d.ts +37 -0
  55. package/dist/runtime/cli-add-workspace-editor-plugin-anchors.js +206 -0
  56. package/dist/runtime/cli-add-workspace-editor-plugin-source-emitters.d.ts +47 -0
  57. package/dist/runtime/cli-add-workspace-editor-plugin-source-emitters.js +219 -0
  58. package/dist/runtime/cli-add-workspace-editor-plugin.d.ts +22 -0
  59. package/dist/runtime/cli-add-workspace-editor-plugin.js +78 -0
  60. package/dist/runtime/cli-add-workspace-hooked-block.d.ts +23 -0
  61. package/dist/runtime/cli-add-workspace-hooked-block.js +57 -0
  62. package/dist/runtime/cli-add-workspace-integration-env-files.d.ts +33 -0
  63. package/dist/runtime/cli-add-workspace-integration-env-files.js +65 -0
  64. package/dist/runtime/cli-add-workspace-integration-env-package-json.d.ts +38 -0
  65. package/dist/runtime/cli-add-workspace-integration-env-package-json.js +122 -0
  66. package/dist/runtime/cli-add-workspace-integration-env-source-emitters.d.ts +44 -0
  67. package/dist/runtime/cli-add-workspace-integration-env-source-emitters.js +262 -0
  68. package/dist/runtime/cli-add-workspace-integration-env.js +5 -345
  69. package/dist/runtime/cli-add-workspace-pattern-anchors.d.ts +10 -0
  70. package/dist/runtime/cli-add-workspace-pattern-anchors.js +95 -0
  71. package/dist/runtime/cli-add-workspace-pattern-options.d.ts +20 -0
  72. package/dist/runtime/cli-add-workspace-pattern-options.js +113 -0
  73. package/dist/runtime/cli-add-workspace-pattern-source-emitters.d.ts +20 -0
  74. package/dist/runtime/cli-add-workspace-pattern-source-emitters.js +57 -0
  75. package/dist/runtime/cli-add-workspace-pattern.d.ts +42 -0
  76. package/dist/runtime/cli-add-workspace-pattern.js +99 -0
  77. package/dist/runtime/cli-add-workspace-post-meta.js +1 -1
  78. package/dist/runtime/cli-add-workspace-registration-hooks.d.ts +50 -0
  79. package/dist/runtime/cli-add-workspace-registration-hooks.js +162 -0
  80. package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +6 -9
  81. package/dist/runtime/cli-add-workspace-rest-anchors.js +6 -466
  82. package/dist/runtime/cli-add-workspace-rest-bootstrap-anchors.d.ts +17 -0
  83. package/dist/runtime/cli-add-workspace-rest-bootstrap-anchors.js +108 -0
  84. package/dist/runtime/cli-add-workspace-rest-contract-sync-anchors.d.ts +9 -0
  85. package/dist/runtime/cli-add-workspace-rest-contract-sync-anchors.js +142 -0
  86. package/dist/runtime/cli-add-workspace-rest-generated-source-emitters.d.ts +51 -0
  87. package/dist/runtime/cli-add-workspace-rest-generated-source-emitters.js +415 -0
  88. package/dist/runtime/cli-add-workspace-rest-generated.js +5 -3
  89. package/dist/runtime/cli-add-workspace-rest-manual-source-emitters.d.ts +80 -0
  90. package/dist/runtime/cli-add-workspace-rest-manual-source-emitters.js +238 -0
  91. package/dist/runtime/cli-add-workspace-rest-manual.js +3 -16
  92. package/dist/runtime/cli-add-workspace-rest-php-templates.d.ts +1 -7
  93. package/dist/runtime/cli-add-workspace-rest-php-templates.js +3 -322
  94. package/dist/runtime/cli-add-workspace-rest-resource-php-routing-template.d.ts +33 -0
  95. package/dist/runtime/cli-add-workspace-rest-resource-php-routing-template.js +145 -0
  96. package/dist/runtime/cli-add-workspace-rest-resource-sync-anchors.d.ts +9 -0
  97. package/dist/runtime/cli-add-workspace-rest-resource-sync-anchors.js +162 -0
  98. package/dist/runtime/cli-add-workspace-rest-schema-helper-php-template.d.ts +7 -0
  99. package/dist/runtime/cli-add-workspace-rest-schema-helper-php-template.js +193 -0
  100. package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +5 -99
  101. package/dist/runtime/cli-add-workspace-rest-source-emitters.js +5 -663
  102. package/dist/runtime/cli-add-workspace-rest-source-utils.d.ts +17 -0
  103. package/dist/runtime/cli-add-workspace-rest-source-utils.js +50 -0
  104. package/dist/runtime/cli-add-workspace-rest-sync-script-shared.d.ts +56 -0
  105. package/dist/runtime/cli-add-workspace-rest-sync-script-shared.js +122 -0
  106. package/dist/runtime/cli-add-workspace-rest-types.d.ts +3 -3
  107. package/dist/runtime/cli-add-workspace-variation.d.ts +22 -0
  108. package/dist/runtime/cli-add-workspace-variation.js +162 -0
  109. package/dist/runtime/cli-add-workspace.d.ts +42 -107
  110. package/dist/runtime/cli-add-workspace.js +42 -674
  111. package/dist/runtime/cli-add.d.ts +3 -3
  112. package/dist/runtime/cli-add.js +2 -2
  113. package/dist/runtime/cli-core.d.ts +2 -1
  114. package/dist/runtime/cli-core.js +1 -1
  115. package/dist/runtime/cli-doctor-workspace-bindings.js +59 -0
  116. package/dist/runtime/cli-doctor-workspace-block-addons.js +33 -5
  117. package/dist/runtime/cli-doctor.d.ts +2 -0
  118. package/dist/runtime/cli-doctor.js +13 -2
  119. package/dist/runtime/cli-help.js +6 -4
  120. package/dist/runtime/index.d.ts +5 -2
  121. package/dist/runtime/index.js +4 -2
  122. package/dist/runtime/local-dev-presets.js +2 -1
  123. package/dist/runtime/package-versions.d.ts +1 -0
  124. package/dist/runtime/package-versions.js +10 -2
  125. package/dist/runtime/pattern-catalog.d.ts +122 -0
  126. package/dist/runtime/pattern-catalog.js +471 -0
  127. package/dist/runtime/post-meta-binding-fields.d.ts +46 -0
  128. package/dist/runtime/post-meta-binding-fields.js +135 -0
  129. package/dist/runtime/typia-llm-json-schema.d.ts +24 -0
  130. package/dist/runtime/typia-llm-json-schema.js +33 -0
  131. package/dist/runtime/typia-llm-openapi-constraints.d.ts +20 -0
  132. package/dist/runtime/typia-llm-openapi-constraints.js +254 -0
  133. package/dist/runtime/typia-llm-projection.d.ts +25 -0
  134. package/dist/runtime/typia-llm-projection.js +58 -0
  135. package/dist/runtime/typia-llm-render.d.ts +21 -0
  136. package/dist/runtime/typia-llm-render.js +252 -0
  137. package/dist/runtime/typia-llm-sync.d.ts +10 -0
  138. package/dist/runtime/typia-llm-sync.js +63 -0
  139. package/dist/runtime/typia-llm-types.d.ts +197 -0
  140. package/dist/runtime/typia-llm-types.js +1 -0
  141. package/dist/runtime/typia-llm.d.ts +9 -255
  142. package/dist/runtime/typia-llm.js +5 -634
  143. package/dist/runtime/workspace-inventory-mutations.js +13 -0
  144. package/dist/runtime/workspace-inventory-section-descriptors.js +9 -1
  145. package/dist/runtime/workspace-inventory-templates.d.ts +2 -2
  146. package/dist/runtime/workspace-inventory-templates.js +9 -1
  147. package/dist/runtime/workspace-inventory-types.d.ts +9 -1
  148. package/package.json +8 -3
  149. package/templates/_shared/compound/core/scripts/block-config.ts.mustache +22 -0
  150. package/templates/_shared/compound/core/scripts/sync-types-to-block-json.ts.mustache +103 -2
  151. package/templates/_shared/compound/core/src/inner-blocks-templates.ts.mustache +13 -0
  152. package/templates/_shared/compound/persistence/scripts/block-config.ts.mustache +22 -1
@@ -0,0 +1,206 @@
1
+ import { promises as fsp } from "node:fs";
2
+ import path from "node:path";
3
+ import { getWorkspaceBootstrapPath, patchFile, } from "./cli-add-shared.js";
4
+ import { appendPhpSnippetBeforeClosingTag, insertPhpSnippetBeforeWorkspaceAnchors, } from "./cli-add-workspace-mutation.js";
5
+ import { pathExists, readOptionalUtf8File } from "./fs-async.js";
6
+ import { findPhpFunctionRange, hasPhpFunctionDefinition, replacePhpFunctionDefinition, } from "./php-utils.js";
7
+ import { readWorkspaceInventoryAsync } from "./workspace-inventory.js";
8
+ const EDITOR_PLUGIN_EDITOR_SCRIPT = "build/editor-plugins/index.js";
9
+ const EDITOR_PLUGIN_EDITOR_ASSET = "build/editor-plugins/index.asset.php";
10
+ const EDITOR_PLUGIN_EDITOR_STYLE = "build/editor-plugins/style-index.css";
11
+ const EDITOR_PLUGIN_EDITOR_STYLE_RTL = "build/editor-plugins/style-index-rtl.css";
12
+ function buildEditorPluginRegistrySource(editorPluginSlugs) {
13
+ const importLines = editorPluginSlugs
14
+ .map((editorPluginSlug) => `import './${editorPluginSlug}';`)
15
+ .join("\n");
16
+ return `${importLines}${importLines ? "\n\n" : ""}// wp-typia add editor-plugin entries\n`;
17
+ }
18
+ /**
19
+ * Ensures the workspace bootstrap enqueues the shared editor plugin bundle.
20
+ *
21
+ * @param workspace Official workspace metadata used to locate and patch the bootstrap file.
22
+ * @returns A promise that resolves after the bootstrap anchors are present.
23
+ */
24
+ export async function ensureEditorPluginBootstrapAnchors(workspace) {
25
+ const bootstrapPath = getWorkspaceBootstrapPath(workspace);
26
+ await patchFile(bootstrapPath, (source) => {
27
+ let nextSource = source;
28
+ const workspaceBaseName = workspace.packageName.split("/").pop() ?? workspace.packageName;
29
+ const enqueueFunctionName = `${workspace.workspace.phpPrefix}_enqueue_editor_plugins_editor`;
30
+ const enqueueHook = `add_action( 'enqueue_block_editor_assets', '${enqueueFunctionName}' );`;
31
+ const enqueueFunction = `
32
+
33
+ function ${enqueueFunctionName}() {
34
+ \t$script_path = __DIR__ . '/${EDITOR_PLUGIN_EDITOR_SCRIPT}';
35
+ \t$asset_path = __DIR__ . '/${EDITOR_PLUGIN_EDITOR_ASSET}';
36
+ \t$style_path = __DIR__ . '/${EDITOR_PLUGIN_EDITOR_STYLE}';
37
+ \t$style_rtl_path = __DIR__ . '/${EDITOR_PLUGIN_EDITOR_STYLE_RTL}';
38
+
39
+ \tif ( ! file_exists( $script_path ) || ! file_exists( $asset_path ) ) {
40
+ \t\treturn;
41
+ \t}
42
+
43
+ \t$asset = require $asset_path;
44
+ \tif ( ! is_array( $asset ) ) {
45
+ \t\t$asset = array();
46
+ \t}
47
+
48
+ \twp_enqueue_script(
49
+ \t\t'${workspaceBaseName}-editor-plugins',
50
+ \t\tplugins_url( '${EDITOR_PLUGIN_EDITOR_SCRIPT}', __FILE__ ),
51
+ \t\tisset( $asset['dependencies'] ) && is_array( $asset['dependencies'] ) ? $asset['dependencies'] : array(),
52
+ \t\tisset( $asset['version'] ) ? $asset['version'] : filemtime( $script_path ),
53
+ \t\ttrue
54
+ \t);
55
+
56
+ \tif ( file_exists( $style_path ) ) {
57
+ \t\twp_enqueue_style(
58
+ \t\t\t'${workspaceBaseName}-editor-plugins',
59
+ \t\t\tplugins_url( '${EDITOR_PLUGIN_EDITOR_STYLE}', __FILE__ ),
60
+ \t\t\tarray(),
61
+ \t\t\tisset( $asset['version'] ) ? $asset['version'] : filemtime( $style_path )
62
+ \t\t);
63
+ \t\tif ( file_exists( $style_rtl_path ) ) {
64
+ \t\t\twp_style_add_data( '${workspaceBaseName}-editor-plugins', 'rtl', 'replace' );
65
+ \t\t}
66
+ \t}
67
+ }
68
+ `;
69
+ if (!hasPhpFunctionDefinition(nextSource, enqueueFunctionName)) {
70
+ nextSource = insertPhpSnippetBeforeWorkspaceAnchors(nextSource, enqueueFunction);
71
+ }
72
+ else {
73
+ const requiredReferences = [
74
+ EDITOR_PLUGIN_EDITOR_SCRIPT,
75
+ EDITOR_PLUGIN_EDITOR_ASSET,
76
+ EDITOR_PLUGIN_EDITOR_STYLE,
77
+ EDITOR_PLUGIN_EDITOR_STYLE_RTL,
78
+ "wp_style_add_data",
79
+ ];
80
+ const functionRange = findPhpFunctionRange(nextSource, enqueueFunctionName);
81
+ const functionSource = functionRange
82
+ ? nextSource.slice(functionRange.start, functionRange.end)
83
+ : "";
84
+ const missingReferences = requiredReferences.filter((reference) => !functionSource.includes(reference));
85
+ if (missingReferences.length > 0) {
86
+ const replacedSource = replacePhpFunctionDefinition(nextSource, enqueueFunctionName, enqueueFunction);
87
+ if (!replacedSource) {
88
+ throw new Error(`Unable to repair ${path.basename(bootstrapPath)} for ${enqueueFunctionName}.`);
89
+ }
90
+ nextSource = replacedSource;
91
+ }
92
+ }
93
+ if (!nextSource.includes(enqueueHook)) {
94
+ nextSource = appendPhpSnippetBeforeClosingTag(nextSource, enqueueHook);
95
+ }
96
+ return nextSource;
97
+ });
98
+ }
99
+ /**
100
+ * Ensures the workspace build script includes the shared editor plugin entry.
101
+ *
102
+ * @param workspace Official workspace metadata used to locate the build script.
103
+ * @returns A promise that resolves after the build script anchors are present.
104
+ */
105
+ export async function ensureEditorPluginBuildScriptAnchors(workspace) {
106
+ const buildScriptPath = path.join(workspace.projectDir, "scripts", "build-workspace.mjs");
107
+ await patchFile(buildScriptPath, (source) => {
108
+ if (/['"]src\/editor-plugins\/index\.(?:ts|js)['"]/u.test(source)) {
109
+ return source;
110
+ }
111
+ const legacySharedEntriesPattern = /\[\s*['"]src\/bindings\/index\.ts['"]\s*,\s*['"]src\/bindings\/index\.js['"]\s*(?:,\s*)?\]/u;
112
+ const nextSource = source.replace(legacySharedEntriesPattern, `[
113
+ \t\t'src/bindings/index.ts',
114
+ \t\t'src/bindings/index.js',
115
+ \t\t'src/editor-plugins/index.ts',
116
+ \t\t'src/editor-plugins/index.js',
117
+ \t]`);
118
+ if (nextSource === source) {
119
+ throw new Error(`Unable to update ${path.relative(workspace.projectDir, buildScriptPath)} for editor plugin shared entries.`);
120
+ }
121
+ return nextSource;
122
+ });
123
+ }
124
+ /**
125
+ * Ensures the workspace webpack config builds the shared editor plugin entry.
126
+ *
127
+ * @param workspace Official workspace metadata used to locate the webpack config.
128
+ * @returns A promise that resolves after the webpack anchors are present.
129
+ */
130
+ export async function ensureEditorPluginWebpackAnchors(workspace) {
131
+ const webpackConfigPath = path.join(workspace.projectDir, "webpack.config.js");
132
+ await patchFile(webpackConfigPath, (source) => {
133
+ if (/['"]editor-plugins\/index['"]/u.test(source)) {
134
+ return source;
135
+ }
136
+ const legacySharedEntriesBlockPattern = /for\s*\(\s*const\s+relativePath\s+of\s+\[\s*['"]src\/bindings\/index\.ts['"]\s*,\s*['"]src\/bindings\/index\.js['"]\s*(?:,\s*)?\]\s*\)\s*\{[\s\S]*?entries\.push\(\s*\[\s*['"]bindings\/index['"]\s*,\s*entryPath\s*\]\s*\);\s*break;\s*\}/u;
137
+ const nextSharedEntriesBlock = [
138
+ "\tfor ( const [ entryName, candidates ] of [",
139
+ "\t\t[",
140
+ "\t\t\t'bindings/index',",
141
+ "\t\t\t[ 'src/bindings/index.ts', 'src/bindings/index.js' ],",
142
+ "\t\t],",
143
+ "\t\t[",
144
+ "\t\t\t'editor-plugins/index',",
145
+ "\t\t\t[ 'src/editor-plugins/index.ts', 'src/editor-plugins/index.js' ],",
146
+ "\t\t],",
147
+ "\t] ) {",
148
+ "\t\tfor ( const relativePath of candidates ) {",
149
+ "\t\t\tconst entryPath = path.resolve( process.cwd(), relativePath );",
150
+ "\t\t\tif ( ! fs.existsSync( entryPath ) ) {",
151
+ "\t\t\t\tcontinue;",
152
+ "\t\t\t}",
153
+ "",
154
+ "\t\t\tentries.push( [ entryName, entryPath ] );",
155
+ "\t\t\tbreak;",
156
+ "\t\t}",
157
+ "\t}",
158
+ ].join("\n");
159
+ const nextSource = source.replace(legacySharedEntriesBlockPattern, nextSharedEntriesBlock);
160
+ if (nextSource === source) {
161
+ throw new Error(`Unable to update ${path.relative(workspace.projectDir, webpackConfigPath)} for editor plugin shared entries.`);
162
+ }
163
+ return nextSource;
164
+ });
165
+ }
166
+ /**
167
+ * Resolves the editor plugin registry path, preferring existing TypeScript or JavaScript registries.
168
+ *
169
+ * @param projectDir Workspace root directory to inspect.
170
+ * @returns A promise for the registry path to read or write.
171
+ */
172
+ export async function resolveEditorPluginRegistryPath(projectDir) {
173
+ const editorPluginsDir = path.join(projectDir, "src", "editor-plugins");
174
+ for (const candidatePath of [
175
+ path.join(editorPluginsDir, "index.ts"),
176
+ path.join(editorPluginsDir, "index.js"),
177
+ ]) {
178
+ if (await pathExists(candidatePath)) {
179
+ return candidatePath;
180
+ }
181
+ }
182
+ return path.join(editorPluginsDir, "index.ts");
183
+ }
184
+ async function readEditorPluginRegistrySlugs(registryPath) {
185
+ const source = await readOptionalUtf8File(registryPath);
186
+ if (source === null) {
187
+ return [];
188
+ }
189
+ return Array.from(source.matchAll(/^\s*import\s+['"]\.\/([^/'"]+)(?:\/index(?:\.[cm]?[jt]sx?)?)?['"];?\s*$/gmu)).map((match) => match[1]);
190
+ }
191
+ /**
192
+ * Rewrites the shared editor plugin registry with the requested plugin slug included.
193
+ *
194
+ * @param projectDir Workspace root directory that owns `src/editor-plugins`.
195
+ * @param editorPluginSlug Editor plugin slug that must be imported by the registry.
196
+ * @returns A promise that resolves after the registry has been written.
197
+ */
198
+ export async function writeEditorPluginRegistry(projectDir, editorPluginSlug) {
199
+ const editorPluginsDir = path.join(projectDir, "src", "editor-plugins");
200
+ const registryPath = await resolveEditorPluginRegistryPath(projectDir);
201
+ await fsp.mkdir(editorPluginsDir, { recursive: true });
202
+ const existingEditorPluginSlugs = (await readWorkspaceInventoryAsync(projectDir)).editorPlugins.map((entry) => entry.slug);
203
+ const existingRegistrySlugs = await readEditorPluginRegistrySlugs(registryPath);
204
+ const nextEditorPluginSlugs = Array.from(new Set([...existingEditorPluginSlugs, ...existingRegistrySlugs, editorPluginSlug])).sort();
205
+ await fsp.writeFile(registryPath, buildEditorPluginRegistrySource(nextEditorPluginSlugs), "utf8");
206
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Render one `scripts/block-config.ts` editor-plugin inventory entry.
3
+ *
4
+ * @param editorPluginSlug Normalized editor-plugin slug.
5
+ * @param slot Canonical editor-plugin slot id.
6
+ * @returns TypeScript source for the inventory entry.
7
+ */
8
+ export declare function buildEditorPluginConfigEntry(editorPluginSlug: string, slot: string): string;
9
+ /**
10
+ * Render the generated editor-plugin model type module.
11
+ *
12
+ * @param editorPluginSlug Normalized editor-plugin slug.
13
+ * @returns TypeScript source for the plugin model type.
14
+ */
15
+ export declare function buildEditorPluginTypesSource(editorPluginSlug: string): string;
16
+ /**
17
+ * Render the generated editor-plugin data module.
18
+ *
19
+ * @param editorPluginSlug Normalized editor-plugin slug.
20
+ * @param slot Canonical editor-plugin slot id.
21
+ * @returns TypeScript source for default plugin data helpers.
22
+ */
23
+ export declare function buildEditorPluginDataSource(editorPluginSlug: string, slot: string): string;
24
+ /**
25
+ * Render the React surface for a generated editor plugin.
26
+ *
27
+ * @param editorPluginSlug Normalized editor-plugin slug.
28
+ * @param slot Canonical editor-plugin slot id.
29
+ * @param textDomain Workspace text domain used for translatable UI strings.
30
+ * @returns TSX source for the generated plugin surface.
31
+ */
32
+ export declare function buildEditorPluginSurfaceSource(editorPluginSlug: string, slot: string, textDomain: string): string;
33
+ /**
34
+ * Render the generated editor-plugin entry module.
35
+ *
36
+ * @param editorPluginSlug Normalized editor-plugin slug.
37
+ * @param namespace Workspace block namespace.
38
+ * @param textDomain Workspace text domain used for translatable UI strings.
39
+ * @returns TSX source for registering the editor plugin.
40
+ */
41
+ export declare function buildEditorPluginEntrySource(editorPluginSlug: string, namespace: string, textDomain: string): string;
42
+ /**
43
+ * Render the generated editor-plugin stylesheet.
44
+ *
45
+ * @returns SCSS source for the generated editor plugin shell.
46
+ */
47
+ export declare function buildEditorPluginStyleSource(): string;
@@ -0,0 +1,219 @@
1
+ import { quoteTsString } from "./cli-add-shared.js";
2
+ import { toPascalCase, toTitleCase } from "./string-case.js";
3
+ /**
4
+ * Render one `scripts/block-config.ts` editor-plugin inventory entry.
5
+ *
6
+ * @param editorPluginSlug Normalized editor-plugin slug.
7
+ * @param slot Canonical editor-plugin slot id.
8
+ * @returns TypeScript source for the inventory entry.
9
+ */
10
+ export function buildEditorPluginConfigEntry(editorPluginSlug, slot) {
11
+ return [
12
+ "\t{",
13
+ `\t\tfile: ${quoteTsString(`src/editor-plugins/${editorPluginSlug}/index.tsx`)},`,
14
+ `\t\tslug: ${quoteTsString(editorPluginSlug)},`,
15
+ `\t\tslot: ${quoteTsString(slot)},`,
16
+ "\t},",
17
+ ].join("\n");
18
+ }
19
+ /**
20
+ * Render the generated editor-plugin model type module.
21
+ *
22
+ * @param editorPluginSlug Normalized editor-plugin slug.
23
+ * @returns TypeScript source for the plugin model type.
24
+ */
25
+ export function buildEditorPluginTypesSource(editorPluginSlug) {
26
+ const typeName = `${toPascalCase(editorPluginSlug)}EditorPluginModel`;
27
+ return `export interface ${typeName} {
28
+ \tprimaryActionLabel: string;
29
+ \tsummary: string;
30
+ }
31
+ `;
32
+ }
33
+ /**
34
+ * Render the generated editor-plugin data module.
35
+ *
36
+ * @param editorPluginSlug Normalized editor-plugin slug.
37
+ * @param slot Canonical editor-plugin slot id.
38
+ * @returns TypeScript source for default plugin data helpers.
39
+ */
40
+ export function buildEditorPluginDataSource(editorPluginSlug, slot) {
41
+ const typeName = `${toPascalCase(editorPluginSlug)}EditorPluginModel`;
42
+ const pluginTitle = toTitleCase(editorPluginSlug);
43
+ const modelFactoryName = `get${toPascalCase(editorPluginSlug)}EditorPluginModel`;
44
+ const enabledFactoryName = `is${toPascalCase(editorPluginSlug)}Enabled`;
45
+ return `import type { ${typeName} } from './types';
46
+
47
+ export const EDITOR_PLUGIN_SLOT = ${quoteTsString(slot)} as const;
48
+ export const REQUIRED_CAPABILITY = 'edit_posts' as const;
49
+
50
+ const DEFAULT_EDITOR_PLUGIN_MODEL: ${typeName} = {
51
+ \tprimaryActionLabel: ${quoteTsString(`Review ${pluginTitle}`)},
52
+ \tsummary: ${quoteTsString(`Replace this summary with your ${pluginTitle} workflow state.`)},
53
+ };
54
+
55
+ export function ${modelFactoryName}(): ${typeName} {
56
+ \treturn DEFAULT_EDITOR_PLUGIN_MODEL;
57
+ }
58
+
59
+ export function ${enabledFactoryName}(): boolean {
60
+ \treturn true;
61
+ }
62
+ `;
63
+ }
64
+ /**
65
+ * Render the React surface for a generated editor plugin.
66
+ *
67
+ * @param editorPluginSlug Normalized editor-plugin slug.
68
+ * @param slot Canonical editor-plugin slot id.
69
+ * @param textDomain Workspace text domain used for translatable UI strings.
70
+ * @returns TSX source for the generated plugin surface.
71
+ */
72
+ export function buildEditorPluginSurfaceSource(editorPluginSlug, slot, textDomain) {
73
+ const pascalName = toPascalCase(editorPluginSlug);
74
+ const modelFactoryName = `get${pascalName}EditorPluginModel`;
75
+ const enabledFactoryName = `is${pascalName}Enabled`;
76
+ const componentName = `${pascalName}Surface`;
77
+ if (slot === "document-setting-panel") {
78
+ return `import { Button } from '@wordpress/components';
79
+ import { PluginDocumentSettingPanel } from '@wordpress/editor';
80
+ import { __ } from '@wordpress/i18n';
81
+
82
+ import { ${modelFactoryName}, ${enabledFactoryName} } from './data';
83
+ import './style.scss';
84
+
85
+ export interface ${componentName}Props {
86
+ \tsurfaceName: string;
87
+ \ttitle: string;
88
+ }
89
+
90
+ export function ${componentName}( {
91
+ \tsurfaceName,
92
+ \ttitle,
93
+ }: ${componentName}Props ) {
94
+ \tif ( ! ${enabledFactoryName}() ) {
95
+ \t\treturn null;
96
+ \t}
97
+
98
+ \tconst editorPluginModel = ${modelFactoryName}();
99
+
100
+ \treturn (
101
+ \t\t<PluginDocumentSettingPanel
102
+ \t\t\tclassName="wp-typia-editor-plugin-shell"
103
+ \t\t\tname={ surfaceName }
104
+ \t\t\ttitle={ title }
105
+ \t\t>
106
+ \t\t\t<p>{ editorPluginModel.summary }</p>
107
+ \t\t\t<Button variant="secondary">
108
+ \t\t\t\t{ editorPluginModel.primaryActionLabel }
109
+ \t\t\t</Button>
110
+ \t\t\t<p className="wp-typia-editor-plugin-shell__hint">
111
+ \t\t\t\t{ __( 'Use data.ts to add post type, capability, or editor context guards before showing this panel.', ${quoteTsString(textDomain)} ) }
112
+ \t\t\t</p>
113
+ \t\t</PluginDocumentSettingPanel>
114
+ \t);
115
+ }
116
+ `;
117
+ }
118
+ return `import { Button, PanelBody } from '@wordpress/components';
119
+ import { PluginSidebar, PluginSidebarMoreMenuItem } from '@wordpress/editor';
120
+ import { __ } from '@wordpress/i18n';
121
+
122
+ import { ${modelFactoryName}, ${enabledFactoryName} } from './data';
123
+ import './style.scss';
124
+
125
+ export interface ${componentName}Props {
126
+ \tsurfaceName: string;
127
+ \ttitle: string;
128
+ }
129
+
130
+ export function ${componentName}( {
131
+ \tsurfaceName,
132
+ \ttitle,
133
+ }: ${componentName}Props ) {
134
+ \tif ( ! ${enabledFactoryName}() ) {
135
+ \t\treturn null;
136
+ \t}
137
+
138
+ \tconst editorPluginModel = ${modelFactoryName}();
139
+
140
+ \treturn (
141
+ \t\t<>
142
+ \t\t\t<PluginSidebarMoreMenuItem target={ surfaceName }>
143
+ \t\t\t\t{ title }
144
+ \t\t\t</PluginSidebarMoreMenuItem>
145
+ \t\t\t<PluginSidebar name={ surfaceName } title={ title }>
146
+ \t\t\t\t<div className="wp-typia-editor-plugin-shell">
147
+ \t\t\t\t\t<PanelBody
148
+ \t\t\t\t\t\tinitialOpen
149
+ \t\t\t\t\t\ttitle={ __( 'Document workflow', ${quoteTsString(textDomain)} ) }
150
+ \t\t\t\t\t>
151
+ \t\t\t\t\t\t<p>{ editorPluginModel.summary }</p>
152
+ \t\t\t\t\t\t<Button variant="secondary">
153
+ \t\t\t\t\t\t\t{ editorPluginModel.primaryActionLabel }
154
+ \t\t\t\t\t\t</Button>
155
+ \t\t\t\t\t</PanelBody>
156
+ \t\t\t\t</div>
157
+ \t\t\t</PluginSidebar>
158
+ \t\t</>
159
+ \t);
160
+ }
161
+ `;
162
+ }
163
+ /**
164
+ * Render the generated editor-plugin entry module.
165
+ *
166
+ * @param editorPluginSlug Normalized editor-plugin slug.
167
+ * @param namespace Workspace block namespace.
168
+ * @param textDomain Workspace text domain used for translatable UI strings.
169
+ * @returns TSX source for registering the editor plugin.
170
+ */
171
+ export function buildEditorPluginEntrySource(editorPluginSlug, namespace, textDomain) {
172
+ const pascalName = toPascalCase(editorPluginSlug);
173
+ const componentName = `${pascalName}Surface`;
174
+ const pluginName = `${namespace}-${editorPluginSlug}`;
175
+ const surfaceName = `${pluginName}-surface`;
176
+ const pluginTitle = toTitleCase(editorPluginSlug);
177
+ return `import { registerPlugin } from '@wordpress/plugins';
178
+ import { __ } from '@wordpress/i18n';
179
+
180
+ import { REQUIRED_CAPABILITY } from './data';
181
+ import { ${componentName} } from './Surface';
182
+
183
+ const EDITOR_PLUGIN_NAME = ${quoteTsString(pluginName)};
184
+ const EDITOR_PLUGIN_SURFACE_NAME = ${quoteTsString(surfaceName)};
185
+ const EDITOR_PLUGIN_TITLE = __( ${quoteTsString(pluginTitle)}, ${quoteTsString(textDomain)} );
186
+
187
+ registerPlugin( EDITOR_PLUGIN_NAME, {
188
+ \ticon: 'admin-generic',
189
+ \trender: () => (
190
+ \t\t<${componentName}
191
+ \t\t\tsurfaceName={ EDITOR_PLUGIN_SURFACE_NAME }
192
+ \t\t\ttitle={ EDITOR_PLUGIN_TITLE }
193
+ \t\t/>
194
+ \t),
195
+ } );
196
+
197
+ export { REQUIRED_CAPABILITY };
198
+ `;
199
+ }
200
+ /**
201
+ * Render the generated editor-plugin stylesheet.
202
+ *
203
+ * @returns SCSS source for the generated editor plugin shell.
204
+ */
205
+ export function buildEditorPluginStyleSource() {
206
+ return `.wp-typia-editor-plugin-shell {
207
+ \tpadding: 16px;
208
+ }
209
+
210
+ .wp-typia-editor-plugin-shell p {
211
+ \tmargin: 0 0 12px;
212
+ }
213
+
214
+ .wp-typia-editor-plugin-shell__hint {
215
+ \tcolor: #757575;
216
+ \tfont-size: 12px;
217
+ }
218
+ `;
219
+ }
@@ -0,0 +1,22 @@
1
+ import { type RunAddEditorPluginCommandOptions } from "./cli-add-shared.js";
2
+ export { ensureEditorPluginBootstrapAnchors, ensureEditorPluginBuildScriptAnchors, ensureEditorPluginWebpackAnchors, resolveEditorPluginRegistryPath, writeEditorPluginRegistry, } from "./cli-add-workspace-editor-plugin-anchors.js";
3
+ /**
4
+ * Add one document-level editor plugin scaffold to an official workspace project.
5
+ *
6
+ * @param options Command options for the editor-plugin scaffold workflow.
7
+ * @param options.cwd Working directory used to resolve the nearest official workspace.
8
+ * Defaults to `process.cwd()`.
9
+ * @param options.editorPluginName Human-entered editor-plugin name that will be
10
+ * normalized and validated before files are written.
11
+ * @param options.slot Optional editor plugin shell slot. Defaults to `sidebar`.
12
+ * @returns A promise that resolves with the normalized `editorPluginSlug`, chosen
13
+ * `slot`, and owning `projectDir` after the scaffold files and inventory entry
14
+ * are written successfully.
15
+ * @throws {Error} When the command is run outside an official workspace, when the
16
+ * slug or slot is invalid, or when a conflicting file or inventory entry exists.
17
+ */
18
+ export declare function runAddEditorPluginCommand({ cwd, editorPluginName, slot, }: RunAddEditorPluginCommandOptions): Promise<{
19
+ editorPluginSlug: string;
20
+ projectDir: string;
21
+ slot: string;
22
+ }>;
@@ -0,0 +1,78 @@
1
+ import { promises as fsp } from "node:fs";
2
+ import path from "node:path";
3
+ import { assertEditorPluginDoesNotExist, assertValidEditorPluginSlot, assertValidGeneratedSlug, getWorkspaceBootstrapPath, normalizeBlockSlug, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
4
+ import { ensureEditorPluginBootstrapAnchors, ensureEditorPluginBuildScriptAnchors, ensureEditorPluginWebpackAnchors, resolveEditorPluginRegistryPath, writeEditorPluginRegistry, } from "./cli-add-workspace-editor-plugin-anchors.js";
5
+ import { buildEditorPluginConfigEntry, buildEditorPluginDataSource, buildEditorPluginEntrySource, buildEditorPluginStyleSource, buildEditorPluginSurfaceSource, buildEditorPluginTypesSource, } from "./cli-add-workspace-editor-plugin-source-emitters.js";
6
+ import { appendWorkspaceInventoryEntries, readWorkspaceInventoryAsync, } from "./workspace-inventory.js";
7
+ import { resolveWorkspaceProject } from "./workspace-project.js";
8
+ export { ensureEditorPluginBootstrapAnchors, ensureEditorPluginBuildScriptAnchors, ensureEditorPluginWebpackAnchors, resolveEditorPluginRegistryPath, writeEditorPluginRegistry, } from "./cli-add-workspace-editor-plugin-anchors.js";
9
+ /**
10
+ * Add one document-level editor plugin scaffold to an official workspace project.
11
+ *
12
+ * @param options Command options for the editor-plugin scaffold workflow.
13
+ * @param options.cwd Working directory used to resolve the nearest official workspace.
14
+ * Defaults to `process.cwd()`.
15
+ * @param options.editorPluginName Human-entered editor-plugin name that will be
16
+ * normalized and validated before files are written.
17
+ * @param options.slot Optional editor plugin shell slot. Defaults to `sidebar`.
18
+ * @returns A promise that resolves with the normalized `editorPluginSlug`, chosen
19
+ * `slot`, and owning `projectDir` after the scaffold files and inventory entry
20
+ * are written successfully.
21
+ * @throws {Error} When the command is run outside an official workspace, when the
22
+ * slug or slot is invalid, or when a conflicting file or inventory entry exists.
23
+ */
24
+ export async function runAddEditorPluginCommand({ cwd = process.cwd(), editorPluginName, slot, }) {
25
+ const workspace = resolveWorkspaceProject(cwd);
26
+ const editorPluginSlug = assertValidGeneratedSlug("Editor plugin name", normalizeBlockSlug(editorPluginName), "wp-typia add editor-plugin <name> [--slot <sidebar|document-setting-panel>]");
27
+ const resolvedSlot = assertValidEditorPluginSlot(slot);
28
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
29
+ assertEditorPluginDoesNotExist(workspace.projectDir, editorPluginSlug, inventory);
30
+ const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
31
+ const bootstrapPath = getWorkspaceBootstrapPath(workspace);
32
+ const buildScriptPath = path.join(workspace.projectDir, "scripts", "build-workspace.mjs");
33
+ const editorPluginsIndexPath = await resolveEditorPluginRegistryPath(workspace.projectDir);
34
+ const webpackConfigPath = path.join(workspace.projectDir, "webpack.config.js");
35
+ const editorPluginDir = path.join(workspace.projectDir, "src", "editor-plugins", editorPluginSlug);
36
+ const entryFilePath = path.join(editorPluginDir, "index.tsx");
37
+ const surfaceFilePath = path.join(editorPluginDir, "Surface.tsx");
38
+ const dataFilePath = path.join(editorPluginDir, "data.ts");
39
+ const typesFilePath = path.join(editorPluginDir, "types.ts");
40
+ const styleFilePath = path.join(editorPluginDir, "style.scss");
41
+ const mutationSnapshot = {
42
+ fileSources: await snapshotWorkspaceFiles([
43
+ blockConfigPath,
44
+ bootstrapPath,
45
+ buildScriptPath,
46
+ editorPluginsIndexPath,
47
+ webpackConfigPath,
48
+ ]),
49
+ snapshotDirs: [],
50
+ targetPaths: [editorPluginDir],
51
+ };
52
+ try {
53
+ await fsp.mkdir(editorPluginDir, { recursive: true });
54
+ await ensureEditorPluginBootstrapAnchors(workspace);
55
+ await ensureEditorPluginBuildScriptAnchors(workspace);
56
+ await ensureEditorPluginWebpackAnchors(workspace);
57
+ await fsp.writeFile(entryFilePath, buildEditorPluginEntrySource(editorPluginSlug, workspace.workspace.namespace, workspace.workspace.textDomain), "utf8");
58
+ await fsp.writeFile(surfaceFilePath, buildEditorPluginSurfaceSource(editorPluginSlug, resolvedSlot, workspace.workspace.textDomain), "utf8");
59
+ await fsp.writeFile(dataFilePath, buildEditorPluginDataSource(editorPluginSlug, resolvedSlot), "utf8");
60
+ await fsp.writeFile(typesFilePath, buildEditorPluginTypesSource(editorPluginSlug), "utf8");
61
+ await fsp.writeFile(styleFilePath, buildEditorPluginStyleSource(), "utf8");
62
+ await writeEditorPluginRegistry(workspace.projectDir, editorPluginSlug);
63
+ await appendWorkspaceInventoryEntries(workspace.projectDir, {
64
+ editorPluginEntries: [
65
+ buildEditorPluginConfigEntry(editorPluginSlug, resolvedSlot),
66
+ ],
67
+ });
68
+ return {
69
+ editorPluginSlug,
70
+ projectDir: workspace.projectDir,
71
+ slot: resolvedSlot,
72
+ };
73
+ }
74
+ catch (error) {
75
+ await rollbackWorkspaceMutation(mutationSnapshot);
76
+ throw error;
77
+ }
78
+ }
@@ -0,0 +1,23 @@
1
+ import type { HookedBlockPositionId } from "./hooked-blocks.js";
2
+ import { type RunAddHookedBlockCommandOptions } from "./cli-add-shared.js";
3
+ /**
4
+ * Add one `blockHooks` entry to an existing official workspace block.
5
+ *
6
+ * @param options Command options for the hooked-block workflow.
7
+ * @param options.anchorBlockName Full block name that will anchor the insertion.
8
+ * @param options.blockName Existing workspace block slug to patch.
9
+ * @param options.cwd Working directory used to resolve the nearest official workspace.
10
+ * Defaults to `process.cwd()`.
11
+ * @param options.position Hook position to store in `block.json`.
12
+ * @returns A promise that resolves with the normalized target block slug, anchor
13
+ * block name, position, and owning project directory after `block.json` is written.
14
+ * @throws {Error} When the command is run outside an official workspace, when
15
+ * the target block is unknown, when required flags are missing, or when the
16
+ * block already defines a hook for the requested anchor.
17
+ */
18
+ export declare function runAddHookedBlockCommand({ anchorBlockName, blockName, cwd, position, }: RunAddHookedBlockCommandOptions): Promise<{
19
+ anchorBlockName: string;
20
+ blockSlug: string;
21
+ position: HookedBlockPositionId;
22
+ projectDir: string;
23
+ }>;
@@ -0,0 +1,57 @@
1
+ import { promises as fsp } from "node:fs";
2
+ import path from "node:path";
3
+ import { assertValidHookAnchor, assertValidHookedBlockPosition, getMutableBlockHooks, normalizeBlockSlug, readWorkspaceBlockJson, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
4
+ import { readWorkspaceInventoryAsync } from "./workspace-inventory.js";
5
+ import { resolveWorkspaceProject } from "./workspace-project.js";
6
+ /**
7
+ * Add one `blockHooks` entry to an existing official workspace block.
8
+ *
9
+ * @param options Command options for the hooked-block workflow.
10
+ * @param options.anchorBlockName Full block name that will anchor the insertion.
11
+ * @param options.blockName Existing workspace block slug to patch.
12
+ * @param options.cwd Working directory used to resolve the nearest official workspace.
13
+ * Defaults to `process.cwd()`.
14
+ * @param options.position Hook position to store in `block.json`.
15
+ * @returns A promise that resolves with the normalized target block slug, anchor
16
+ * block name, position, and owning project directory after `block.json` is written.
17
+ * @throws {Error} When the command is run outside an official workspace, when
18
+ * the target block is unknown, when required flags are missing, or when the
19
+ * block already defines a hook for the requested anchor.
20
+ */
21
+ export async function runAddHookedBlockCommand({ anchorBlockName, blockName, cwd = process.cwd(), position, }) {
22
+ const workspace = resolveWorkspaceProject(cwd);
23
+ const blockSlug = normalizeBlockSlug(blockName);
24
+ const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
25
+ resolveWorkspaceBlock(inventory, blockSlug);
26
+ const resolvedAnchorBlockName = assertValidHookAnchor(anchorBlockName);
27
+ const resolvedPosition = assertValidHookedBlockPosition(position);
28
+ const selfHookAnchor = `${workspace.workspace.namespace}/${blockSlug}`;
29
+ if (resolvedAnchorBlockName === selfHookAnchor) {
30
+ throw new Error("`wp-typia add hooked-block` cannot hook a block relative to its own block name.");
31
+ }
32
+ const { blockJson, blockJsonPath } = await readWorkspaceBlockJson(workspace.projectDir, blockSlug);
33
+ const blockJsonRelativePath = path.relative(workspace.projectDir, blockJsonPath);
34
+ const blockHooks = getMutableBlockHooks(blockJson, blockJsonRelativePath);
35
+ if (Object.prototype.hasOwnProperty.call(blockHooks, resolvedAnchorBlockName)) {
36
+ throw new Error(`${blockJsonRelativePath} already defines a blockHooks entry for "${resolvedAnchorBlockName}".`);
37
+ }
38
+ const mutationSnapshot = {
39
+ fileSources: await snapshotWorkspaceFiles([blockJsonPath]),
40
+ snapshotDirs: [],
41
+ targetPaths: [],
42
+ };
43
+ try {
44
+ blockHooks[resolvedAnchorBlockName] = resolvedPosition;
45
+ await fsp.writeFile(blockJsonPath, JSON.stringify(blockJson, null, "\t"), "utf8");
46
+ return {
47
+ anchorBlockName: resolvedAnchorBlockName,
48
+ blockSlug,
49
+ position: resolvedPosition,
50
+ projectDir: workspace.projectDir,
51
+ };
52
+ }
53
+ catch (error) {
54
+ await rollbackWorkspaceMutation(mutationSnapshot);
55
+ throw error;
56
+ }
57
+ }