@wp-typia/project-tools 0.16.14 → 0.18.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 (79) hide show
  1. package/dist/runtime/block-generator-service-core.d.ts +1 -1
  2. package/dist/runtime/block-generator-service-core.js +2 -1
  3. package/dist/runtime/block-generator-service-spec.d.ts +8 -1
  4. package/dist/runtime/block-generator-service-spec.js +27 -0
  5. package/dist/runtime/built-in-block-artifacts.js +1 -0
  6. package/dist/runtime/built-in-block-code-artifacts.js +14 -1
  7. package/dist/runtime/built-in-block-code-templates/compound-child.d.ts +2 -2
  8. package/dist/runtime/built-in-block-code-templates/compound-child.js +30 -2
  9. package/dist/runtime/built-in-block-code-templates/compound-parent.d.ts +1 -1
  10. package/dist/runtime/built-in-block-code-templates/compound-parent.js +139 -19
  11. package/dist/runtime/built-in-block-code-templates/query-loop.d.ts +1 -0
  12. package/dist/runtime/built-in-block-code-templates/query-loop.js +70 -0
  13. package/dist/runtime/built-in-block-code-templates.d.ts +1 -0
  14. package/dist/runtime/built-in-block-code-templates.js +1 -0
  15. package/dist/runtime/built-in-block-non-ts-artifacts.js +2 -0
  16. package/dist/runtime/cli-add-block.d.ts +2 -1
  17. package/dist/runtime/cli-add-block.js +19 -1
  18. package/dist/runtime/cli-add-shared.d.ts +55 -1
  19. package/dist/runtime/cli-add-shared.js +101 -2
  20. package/dist/runtime/cli-add-workspace-assets.d.ts +21 -1
  21. package/dist/runtime/cli-add-workspace-assets.js +417 -1
  22. package/dist/runtime/cli-add-workspace-rest.d.ts +14 -0
  23. package/dist/runtime/cli-add-workspace-rest.js +1060 -0
  24. package/dist/runtime/cli-add-workspace.d.ts +10 -1
  25. package/dist/runtime/cli-add-workspace.js +10 -1
  26. package/dist/runtime/cli-add.d.ts +3 -3
  27. package/dist/runtime/cli-add.js +2 -2
  28. package/dist/runtime/cli-core.d.ts +3 -1
  29. package/dist/runtime/cli-core.js +2 -1
  30. package/dist/runtime/cli-doctor-workspace.js +135 -1
  31. package/dist/runtime/cli-help.js +10 -5
  32. package/dist/runtime/cli-scaffold.d.ts +11 -2
  33. package/dist/runtime/cli-scaffold.js +137 -36
  34. package/dist/runtime/cli-templates.d.ts +4 -4
  35. package/dist/runtime/cli-templates.js +79 -39
  36. package/dist/runtime/index.d.ts +4 -3
  37. package/dist/runtime/index.js +3 -2
  38. package/dist/runtime/local-dev-presets.js +27 -12
  39. package/dist/runtime/rest-resource-artifacts.d.ts +35 -0
  40. package/dist/runtime/rest-resource-artifacts.js +158 -0
  41. package/dist/runtime/scaffold-answer-resolution.d.ts +1 -1
  42. package/dist/runtime/scaffold-answer-resolution.js +94 -3
  43. package/dist/runtime/scaffold-apply-utils.d.ts +4 -3
  44. package/dist/runtime/scaffold-apply-utils.js +34 -17
  45. package/dist/runtime/scaffold-bootstrap.d.ts +15 -0
  46. package/dist/runtime/scaffold-bootstrap.js +29 -7
  47. package/dist/runtime/scaffold-documents.js +9 -9
  48. package/dist/runtime/scaffold-onboarding.js +17 -1
  49. package/dist/runtime/scaffold-package-manager-files.js +6 -1
  50. package/dist/runtime/scaffold-template-variables.js +6 -0
  51. package/dist/runtime/scaffold.d.ts +15 -1
  52. package/dist/runtime/scaffold.js +50 -8
  53. package/dist/runtime/template-defaults.d.ts +7 -0
  54. package/dist/runtime/template-defaults.js +4 -0
  55. package/dist/runtime/template-registry.d.ts +1 -1
  56. package/dist/runtime/template-registry.js +14 -1
  57. package/dist/runtime/template-render.d.ts +5 -2
  58. package/dist/runtime/template-render.js +9 -3
  59. package/dist/runtime/template-source-contracts.d.ts +11 -0
  60. package/dist/runtime/template-source-external.d.ts +1 -1
  61. package/dist/runtime/template-source-external.js +45 -13
  62. package/dist/runtime/template-source-normalization.d.ts +1 -1
  63. package/dist/runtime/template-source-normalization.js +5 -1
  64. package/dist/runtime/template-source-remote.d.ts +5 -0
  65. package/dist/runtime/template-source-remote.js +33 -0
  66. package/dist/runtime/template-source.js +30 -1
  67. package/dist/runtime/workspace-inventory.d.ts +43 -1
  68. package/dist/runtime/workspace-inventory.js +132 -1
  69. package/dist/runtime/workspace-project.d.ts +1 -1
  70. package/dist/runtime/workspace-project.js +2 -2
  71. package/package.json +3 -3
  72. package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +428 -49
  73. package/templates/query-loop/inc/query-runtime.php.mustache +85 -0
  74. package/templates/query-loop/package.json.mustache +32 -0
  75. package/templates/query-loop/src/patterns/grid.php.mustache +49 -0
  76. package/templates/query-loop/src/patterns/list.php.mustache +48 -0
  77. package/templates/query-loop/src/query-extension.ts.mustache +41 -0
  78. package/templates/query-loop/webpack.config.js.mustache +16 -0
  79. package/templates/query-loop/{{slugKebabCase}}.php.mustache +84 -0
@@ -4,17 +4,67 @@ import path from "node:path";
4
4
  import { resolveWorkspaceProject, } from "./workspace-project.js";
5
5
  import { readWorkspaceInventory, appendWorkspaceInventoryEntries } from "./workspace-inventory.js";
6
6
  import { toTitleCase } from "./string-case.js";
7
- import { assertBindingSourceDoesNotExist, assertPatternDoesNotExist, assertValidGeneratedSlug, getWorkspaceBootstrapPath, normalizeBlockSlug, patchFile, quoteTsString, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
7
+ import { assertBindingSourceDoesNotExist, assertEditorPluginDoesNotExist, assertPatternDoesNotExist, assertValidEditorPluginSlot, assertValidGeneratedSlug, getWorkspaceBootstrapPath, normalizeBlockSlug, patchFile, quoteTsString, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
8
8
  const PATTERN_BOOTSTRAP_CATEGORY = "register_block_pattern_category";
9
9
  const BINDING_SOURCE_SERVER_GLOB = "/src/bindings/*/server.php";
10
10
  const BINDING_SOURCE_EDITOR_SCRIPT = "build/bindings/index.js";
11
11
  const BINDING_SOURCE_EDITOR_ASSET = "build/bindings/index.asset.php";
12
+ const EDITOR_PLUGIN_EDITOR_SCRIPT = "build/editor-plugins/index.js";
13
+ const EDITOR_PLUGIN_EDITOR_ASSET = "build/editor-plugins/index.asset.php";
14
+ const EDITOR_PLUGIN_EDITOR_STYLE = "build/editor-plugins/style-index.css";
15
+ const EDITOR_PLUGIN_EDITOR_STYLE_RTL = "build/editor-plugins/style-index-rtl.css";
12
16
  function escapeRegex(value) {
13
17
  return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
14
18
  }
15
19
  function quotePhpString(value) {
16
20
  return `'${value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'`;
17
21
  }
22
+ function findPhpFunctionRange(source, functionName) {
23
+ const signaturePattern = new RegExp(`function\\s+${escapeRegex(functionName)}\\s*\\(`, "u");
24
+ const signatureMatch = signaturePattern.exec(source);
25
+ if (!signatureMatch || signatureMatch.index === undefined) {
26
+ return null;
27
+ }
28
+ const functionStart = signatureMatch.index;
29
+ const openBraceIndex = source.indexOf("{", functionStart);
30
+ if (openBraceIndex === -1) {
31
+ return null;
32
+ }
33
+ let depth = 0;
34
+ for (let index = openBraceIndex; index < source.length; index += 1) {
35
+ const character = source[index];
36
+ if (character === "{") {
37
+ depth += 1;
38
+ continue;
39
+ }
40
+ if (character !== "}") {
41
+ continue;
42
+ }
43
+ depth -= 1;
44
+ if (depth === 0) {
45
+ let functionEnd = index + 1;
46
+ while (functionEnd < source.length && /[\r\n]/u.test(source[functionEnd] ?? "")) {
47
+ functionEnd += 1;
48
+ }
49
+ return {
50
+ end: functionEnd,
51
+ start: functionStart,
52
+ };
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ function replacePhpFunctionDefinition(source, functionName, replacement) {
58
+ const functionRange = findPhpFunctionRange(source, functionName);
59
+ if (!functionRange) {
60
+ return null;
61
+ }
62
+ return [
63
+ source.slice(0, functionRange.start),
64
+ replacement,
65
+ source.slice(functionRange.end),
66
+ ].join("");
67
+ }
18
68
  function buildPatternConfigEntry(patternSlug) {
19
69
  return [
20
70
  "\t{",
@@ -32,6 +82,22 @@ function buildBindingSourceConfigEntry(bindingSourceSlug) {
32
82
  "\t},",
33
83
  ].join("\n");
34
84
  }
85
+ function buildEditorPluginConfigEntry(editorPluginSlug, slot) {
86
+ return [
87
+ "\t{",
88
+ `\t\tfile: ${quoteTsString(`src/editor-plugins/${editorPluginSlug}/index.tsx`)},`,
89
+ `\t\tslug: ${quoteTsString(editorPluginSlug)},`,
90
+ `\t\tslot: ${quoteTsString(slot)},`,
91
+ "\t},",
92
+ ].join("\n");
93
+ }
94
+ function toPascalCaseFromSlug(slug) {
95
+ return normalizeBlockSlug(slug)
96
+ .split("-")
97
+ .filter(Boolean)
98
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
99
+ .join("");
100
+ }
35
101
  function buildPatternSource(patternSlug, namespace, textDomain) {
36
102
  const patternTitle = toTitleCase(patternSlug);
37
103
  return `<?php
@@ -144,12 +210,137 @@ registerBlockBindingsSource( {
144
210
  } );
145
211
  `;
146
212
  }
213
+ function buildEditorPluginTypesSource(editorPluginSlug) {
214
+ const typeName = `${toPascalCaseFromSlug(editorPluginSlug)}SidebarModel`;
215
+ return `export interface ${typeName} {
216
+ \tprimaryActionLabel: string;
217
+ \tsummary: string;
218
+ }
219
+ `;
220
+ }
221
+ function buildEditorPluginDataSource(editorPluginSlug, slot) {
222
+ const typeName = `${toPascalCaseFromSlug(editorPluginSlug)}SidebarModel`;
223
+ const pluginTitle = toTitleCase(editorPluginSlug);
224
+ const modelFactoryName = `get${toPascalCaseFromSlug(editorPluginSlug)}SidebarModel`;
225
+ const enabledFactoryName = `is${toPascalCaseFromSlug(editorPluginSlug)}Enabled`;
226
+ return `import type { ${typeName} } from './types';
227
+
228
+ export const EDITOR_PLUGIN_SLOT = ${quoteTsString(slot)} as const;
229
+ export const REQUIRED_CAPABILITY = 'edit_posts' as const;
230
+
231
+ const DEFAULT_SIDEBAR_MODEL: ${typeName} = {
232
+ \tprimaryActionLabel: ${quoteTsString(`Review ${pluginTitle}`)},
233
+ \tsummary: ${quoteTsString(`Replace this summary with your ${pluginTitle} workflow state.`)},
234
+ };
235
+
236
+ export function ${modelFactoryName}(): ${typeName} {
237
+ \treturn DEFAULT_SIDEBAR_MODEL;
238
+ }
239
+
240
+ export function ${enabledFactoryName}(): boolean {
241
+ \treturn true;
242
+ }
243
+ `;
244
+ }
245
+ function buildEditorPluginSidebarSource(editorPluginSlug, textDomain) {
246
+ const pascalName = toPascalCaseFromSlug(editorPluginSlug);
247
+ const modelFactoryName = `get${pascalName}SidebarModel`;
248
+ const enabledFactoryName = `is${pascalName}Enabled`;
249
+ const componentName = `${pascalName}Sidebar`;
250
+ return `import { Button, PanelBody } from '@wordpress/components';
251
+ import { PluginSidebar, PluginSidebarMoreMenuItem } from '@wordpress/editor';
252
+ import { __ } from '@wordpress/i18n';
253
+
254
+ import { ${modelFactoryName}, ${enabledFactoryName} } from './data';
255
+ import './style.scss';
256
+
257
+ export interface ${componentName}Props {
258
+ \tpluginName: string;
259
+ \ttitle: string;
260
+ }
261
+
262
+ export function ${componentName}( {
263
+ \tpluginName,
264
+ \ttitle,
265
+ }: ${componentName}Props ) {
266
+ \tif ( ! ${enabledFactoryName}() ) {
267
+ \t\treturn null;
268
+ \t}
269
+
270
+ \tconst sidebarModel = ${modelFactoryName}();
271
+
272
+ \treturn (
273
+ \t\t<>
274
+ \t\t\t<PluginSidebarMoreMenuItem target={ pluginName }>
275
+ \t\t\t\t{ title }
276
+ \t\t\t</PluginSidebarMoreMenuItem>
277
+ \t\t\t<PluginSidebar name={ pluginName } title={ title }>
278
+ \t\t\t\t<div className="wp-typia-editor-plugin-shell">
279
+ \t\t\t\t\t<PanelBody
280
+ \t\t\t\t\t\tinitialOpen
281
+ \t\t\t\t\t\ttitle={ __( 'Document workflow', ${quoteTsString(textDomain)} ) }
282
+ \t\t\t\t\t>
283
+ \t\t\t\t\t\t<p>{ sidebarModel.summary }</p>
284
+ \t\t\t\t\t\t<Button variant="secondary">
285
+ \t\t\t\t\t\t\t{ sidebarModel.primaryActionLabel }
286
+ \t\t\t\t\t\t</Button>
287
+ \t\t\t\t\t</PanelBody>
288
+ \t\t\t\t</div>
289
+ \t\t\t</PluginSidebar>
290
+ \t\t</>
291
+ \t);
292
+ }
293
+ `;
294
+ }
295
+ function buildEditorPluginEntrySource(editorPluginSlug, namespace, textDomain) {
296
+ const pascalName = toPascalCaseFromSlug(editorPluginSlug);
297
+ const componentName = `${pascalName}Sidebar`;
298
+ const pluginName = `${namespace}-${editorPluginSlug}`;
299
+ const pluginTitle = toTitleCase(editorPluginSlug);
300
+ return `import { registerPlugin } from '@wordpress/plugins';
301
+ import { __ } from '@wordpress/i18n';
302
+
303
+ import { REQUIRED_CAPABILITY } from './data';
304
+ import { ${componentName} } from './Sidebar';
305
+
306
+ const EDITOR_PLUGIN_NAME = ${quoteTsString(pluginName)};
307
+ const EDITOR_PLUGIN_TITLE = __( ${quoteTsString(pluginTitle)}, ${quoteTsString(textDomain)} );
308
+
309
+ registerPlugin( EDITOR_PLUGIN_NAME, {
310
+ \ticon: 'admin-generic',
311
+ \trender: () => (
312
+ \t\t<${componentName}
313
+ \t\t\tpluginName={ EDITOR_PLUGIN_NAME }
314
+ \t\t\ttitle={ EDITOR_PLUGIN_TITLE }
315
+ \t\t/>
316
+ \t),
317
+ } );
318
+
319
+ export { REQUIRED_CAPABILITY };
320
+ `;
321
+ }
322
+ function buildEditorPluginStyleSource() {
323
+ return `.wp-typia-editor-plugin-shell {
324
+ \tpadding: 16px;
325
+ }
326
+
327
+ .wp-typia-editor-plugin-shell p {
328
+ \tmargin: 0 0 12px;
329
+ }
330
+ `;
331
+ }
147
332
  function buildBindingSourceIndexSource(bindingSourceSlugs) {
148
333
  const importLines = bindingSourceSlugs
149
334
  .map((bindingSourceSlug) => `import './${bindingSourceSlug}/editor';`)
150
335
  .join("\n");
151
336
  return `${importLines}${importLines ? "\n\n" : ""}// wp-typia add binding-source entries\n`;
152
337
  }
338
+ function buildEditorPluginRegistrySource(editorPluginSlugs) {
339
+ const importLines = editorPluginSlugs
340
+ .map((editorPluginSlug) => `import './${editorPluginSlug}';`)
341
+ .join("\n");
342
+ return `${importLines}${importLines ? "\n\n" : ""}// wp-typia add editor-plugin entries\n`;
343
+ }
153
344
  async function ensurePatternBootstrapAnchors(workspace) {
154
345
  const workspaceBaseName = workspace.packageName.split("/").pop() ?? workspace.packageName;
155
346
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
@@ -288,6 +479,138 @@ function ${bindingEditorEnqueueFunctionName}() {
288
479
  return nextSource;
289
480
  });
290
481
  }
482
+ async function ensureEditorPluginBootstrapAnchors(workspace) {
483
+ const bootstrapPath = getWorkspaceBootstrapPath(workspace);
484
+ await patchFile(bootstrapPath, (source) => {
485
+ let nextSource = source;
486
+ const workspaceBaseName = workspace.packageName.split("/").pop() ?? workspace.packageName;
487
+ const enqueueFunctionName = `${workspace.workspace.phpPrefix}_enqueue_editor_plugins_editor`;
488
+ const enqueueHook = `add_action( 'enqueue_block_editor_assets', '${enqueueFunctionName}' );`;
489
+ const enqueueFunction = `
490
+
491
+ function ${enqueueFunctionName}() {
492
+ \t$script_path = __DIR__ . '/${EDITOR_PLUGIN_EDITOR_SCRIPT}';
493
+ \t$asset_path = __DIR__ . '/${EDITOR_PLUGIN_EDITOR_ASSET}';
494
+ \t$style_path = __DIR__ . '/${EDITOR_PLUGIN_EDITOR_STYLE}';
495
+ \t$style_rtl_path = __DIR__ . '/${EDITOR_PLUGIN_EDITOR_STYLE_RTL}';
496
+
497
+ \tif ( ! file_exists( $script_path ) || ! file_exists( $asset_path ) ) {
498
+ \t\treturn;
499
+ \t}
500
+
501
+ \t$asset = require $asset_path;
502
+ \tif ( ! is_array( $asset ) ) {
503
+ \t\t$asset = array();
504
+ \t}
505
+
506
+ \twp_enqueue_script(
507
+ \t\t'${workspaceBaseName}-editor-plugins',
508
+ \t\tplugins_url( '${EDITOR_PLUGIN_EDITOR_SCRIPT}', __FILE__ ),
509
+ \t\tisset( $asset['dependencies'] ) && is_array( $asset['dependencies'] ) ? $asset['dependencies'] : array(),
510
+ \t\tisset( $asset['version'] ) ? $asset['version'] : filemtime( $script_path ),
511
+ \t\ttrue
512
+ \t);
513
+
514
+ \tif ( file_exists( $style_path ) ) {
515
+ \t\twp_enqueue_style(
516
+ \t\t\t'${workspaceBaseName}-editor-plugins',
517
+ \t\t\tplugins_url( '${EDITOR_PLUGIN_EDITOR_STYLE}', __FILE__ ),
518
+ \t\t\tarray(),
519
+ \t\t\tisset( $asset['version'] ) ? $asset['version'] : filemtime( $style_path )
520
+ \t\t);
521
+ \t\tif ( file_exists( $style_rtl_path ) ) {
522
+ \t\t\twp_style_add_data( '${workspaceBaseName}-editor-plugins', 'rtl', 'replace' );
523
+ \t\t}
524
+ \t}
525
+ }
526
+ `;
527
+ const insertionAnchors = [
528
+ /add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
529
+ /\?>\s*$/u,
530
+ ];
531
+ const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex(functionName)}\\s*\\(`, "u").test(nextSource);
532
+ const insertPhpSnippet = (snippet) => {
533
+ for (const anchor of insertionAnchors) {
534
+ const candidate = nextSource.replace(anchor, (match) => `${snippet}\n${match}`);
535
+ if (candidate !== nextSource) {
536
+ nextSource = candidate;
537
+ return;
538
+ }
539
+ }
540
+ nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
541
+ };
542
+ const appendPhpSnippet = (snippet) => {
543
+ const closingTagPattern = /\?>\s*$/u;
544
+ if (closingTagPattern.test(nextSource)) {
545
+ nextSource = nextSource.replace(closingTagPattern, `${snippet}\n?>`);
546
+ return;
547
+ }
548
+ nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
549
+ };
550
+ if (!hasPhpFunctionDefinition(enqueueFunctionName)) {
551
+ insertPhpSnippet(enqueueFunction);
552
+ }
553
+ else {
554
+ const requiredReferences = [
555
+ EDITOR_PLUGIN_EDITOR_SCRIPT,
556
+ EDITOR_PLUGIN_EDITOR_ASSET,
557
+ EDITOR_PLUGIN_EDITOR_STYLE,
558
+ EDITOR_PLUGIN_EDITOR_STYLE_RTL,
559
+ "wp_style_add_data",
560
+ ];
561
+ const functionRange = findPhpFunctionRange(nextSource, enqueueFunctionName);
562
+ const functionSource = functionRange
563
+ ? nextSource.slice(functionRange.start, functionRange.end)
564
+ : "";
565
+ const missingReferences = requiredReferences.filter((reference) => !functionSource.includes(reference));
566
+ if (missingReferences.length > 0) {
567
+ const replacedSource = replacePhpFunctionDefinition(nextSource, enqueueFunctionName, enqueueFunction);
568
+ if (!replacedSource) {
569
+ throw new Error(`Unable to repair ${path.basename(bootstrapPath)} for ${enqueueFunctionName}.`);
570
+ }
571
+ nextSource = replacedSource;
572
+ }
573
+ }
574
+ if (!nextSource.includes(enqueueHook)) {
575
+ appendPhpSnippet(enqueueHook);
576
+ }
577
+ return nextSource;
578
+ });
579
+ }
580
+ async function ensureEditorPluginBuildScriptAnchors(workspace) {
581
+ const buildScriptPath = path.join(workspace.projectDir, "scripts", "build-workspace.mjs");
582
+ await patchFile(buildScriptPath, (source) => {
583
+ if (/['"]src\/editor-plugins\/index\.(?:ts|js)['"]/u.test(source)) {
584
+ return source;
585
+ }
586
+ const legacySharedEntriesPattern = /\[\s*['"]src\/bindings\/index\.ts['"]\s*,\s*['"]src\/bindings\/index\.js['"]\s*(?:,\s*)?\]/u;
587
+ const nextSource = source.replace(legacySharedEntriesPattern, `[
588
+ \t\t'src/bindings/index.ts',
589
+ \t\t'src/bindings/index.js',
590
+ \t\t'src/editor-plugins/index.ts',
591
+ \t\t'src/editor-plugins/index.js',
592
+ \t]`);
593
+ if (nextSource === source) {
594
+ throw new Error(`Unable to update ${path.relative(workspace.projectDir, buildScriptPath)} for editor plugin shared entries.`);
595
+ }
596
+ return nextSource;
597
+ });
598
+ }
599
+ async function ensureEditorPluginWebpackAnchors(workspace) {
600
+ const webpackConfigPath = path.join(workspace.projectDir, "webpack.config.js");
601
+ await patchFile(webpackConfigPath, (source) => {
602
+ if (/['"]editor-plugins\/index['"]/u.test(source)) {
603
+ return source;
604
+ }
605
+ 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;
606
+ const nextSharedEntriesBlock = `\tfor ( const [ entryName, candidates ] of [\n\t\t[\n\t\t\t'bindings/index',\n\t\t\t[ 'src/bindings/index.ts', 'src/bindings/index.js' ],\n\t\t],\n\t\t[\n\t\t\t'editor-plugins/index',\n\t\t\t[ 'src/editor-plugins/index.ts', 'src/editor-plugins/index.js' ],\n\t\t],\n\t] ) {\n\t\tfor ( const relativePath of candidates ) {\n\t\t\tconst entryPath = path.resolve( process.cwd(), relativePath );\n\t\t\tif ( ! fs.existsSync( entryPath ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tentries.push( [ entryName, entryPath ] );\n\t\t\tbreak;\n\t\t}\n\t}`;
607
+ const nextSource = source.replace(legacySharedEntriesBlockPattern, nextSharedEntriesBlock);
608
+ if (nextSource === source) {
609
+ throw new Error(`Unable to update ${path.relative(workspace.projectDir, webpackConfigPath)} for editor plugin shared entries.`);
610
+ }
611
+ return nextSource;
612
+ });
613
+ }
291
614
  function resolveBindingSourceRegistryPath(projectDir) {
292
615
  const bindingsDir = path.join(projectDir, "src", "bindings");
293
616
  return [path.join(bindingsDir, "index.ts"), path.join(bindingsDir, "index.js")].find((candidatePath) => fs.existsSync(candidatePath)) ?? path.join(bindingsDir, "index.ts");
@@ -303,6 +626,99 @@ async function writeBindingSourceRegistry(projectDir, bindingSourceSlug) {
303
626
  const nextBindingSourceSlugs = Array.from(new Set([...existingBindingSourceSlugs, bindingSourceSlug])).sort();
304
627
  await fsp.writeFile(bindingsIndexPath, buildBindingSourceIndexSource(nextBindingSourceSlugs), "utf8");
305
628
  }
629
+ function resolveEditorPluginRegistryPath(projectDir) {
630
+ const editorPluginsDir = path.join(projectDir, "src", "editor-plugins");
631
+ return [
632
+ path.join(editorPluginsDir, "index.ts"),
633
+ path.join(editorPluginsDir, "index.js"),
634
+ ].find((candidatePath) => fs.existsSync(candidatePath)) ?? path.join(editorPluginsDir, "index.ts");
635
+ }
636
+ function readEditorPluginRegistrySlugs(registryPath) {
637
+ if (!fs.existsSync(registryPath)) {
638
+ return [];
639
+ }
640
+ const source = fs.readFileSync(registryPath, "utf8");
641
+ return Array.from(source.matchAll(/^\s*import\s+['"]\.\/([^/'"]+)(?:\/index(?:\.[cm]?[jt]sx?)?)?['"];?\s*$/gmu)).map((match) => match[1]);
642
+ }
643
+ async function writeEditorPluginRegistry(projectDir, editorPluginSlug) {
644
+ const editorPluginsDir = path.join(projectDir, "src", "editor-plugins");
645
+ const registryPath = resolveEditorPluginRegistryPath(projectDir);
646
+ await fsp.mkdir(editorPluginsDir, { recursive: true });
647
+ const existingEditorPluginSlugs = readWorkspaceInventory(projectDir).editorPlugins.map((entry) => entry.slug);
648
+ const existingRegistrySlugs = readEditorPluginRegistrySlugs(registryPath);
649
+ const nextEditorPluginSlugs = Array.from(new Set([...existingEditorPluginSlugs, ...existingRegistrySlugs, editorPluginSlug])).sort();
650
+ await fsp.writeFile(registryPath, buildEditorPluginRegistrySource(nextEditorPluginSlugs), "utf8");
651
+ }
652
+ /**
653
+ * Add one document-level editor plugin scaffold to an official workspace project.
654
+ *
655
+ * @param options Command options for the editor-plugin scaffold workflow.
656
+ * @param options.cwd Working directory used to resolve the nearest official workspace.
657
+ * Defaults to `process.cwd()`.
658
+ * @param options.editorPluginName Human-entered editor-plugin name that will be
659
+ * normalized and validated before files are written.
660
+ * @param options.slot Optional editor plugin shell slot. Defaults to `PluginSidebar`.
661
+ * @returns A promise that resolves with the normalized `editorPluginSlug`, chosen
662
+ * `slot`, and owning `projectDir` after the scaffold files and inventory entry
663
+ * are written successfully.
664
+ * @throws {Error} When the command is run outside an official workspace, when the
665
+ * slug or slot is invalid, or when a conflicting file or inventory entry exists.
666
+ */
667
+ export async function runAddEditorPluginCommand({ cwd = process.cwd(), editorPluginName, slot, }) {
668
+ const workspace = resolveWorkspaceProject(cwd);
669
+ const editorPluginSlug = assertValidGeneratedSlug("Editor plugin name", normalizeBlockSlug(editorPluginName), "wp-typia add editor-plugin <name> [--slot <PluginSidebar>]");
670
+ const resolvedSlot = assertValidEditorPluginSlot(slot);
671
+ const inventory = readWorkspaceInventory(workspace.projectDir);
672
+ assertEditorPluginDoesNotExist(workspace.projectDir, editorPluginSlug, inventory);
673
+ const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
674
+ const bootstrapPath = getWorkspaceBootstrapPath(workspace);
675
+ const buildScriptPath = path.join(workspace.projectDir, "scripts", "build-workspace.mjs");
676
+ const editorPluginsIndexPath = resolveEditorPluginRegistryPath(workspace.projectDir);
677
+ const webpackConfigPath = path.join(workspace.projectDir, "webpack.config.js");
678
+ const editorPluginDir = path.join(workspace.projectDir, "src", "editor-plugins", editorPluginSlug);
679
+ const entryFilePath = path.join(editorPluginDir, "index.tsx");
680
+ const sidebarFilePath = path.join(editorPluginDir, "Sidebar.tsx");
681
+ const dataFilePath = path.join(editorPluginDir, "data.ts");
682
+ const typesFilePath = path.join(editorPluginDir, "types.ts");
683
+ const styleFilePath = path.join(editorPluginDir, "style.scss");
684
+ const mutationSnapshot = {
685
+ fileSources: await snapshotWorkspaceFiles([
686
+ blockConfigPath,
687
+ bootstrapPath,
688
+ buildScriptPath,
689
+ editorPluginsIndexPath,
690
+ webpackConfigPath,
691
+ ]),
692
+ snapshotDirs: [],
693
+ targetPaths: [editorPluginDir],
694
+ };
695
+ try {
696
+ await fsp.mkdir(editorPluginDir, { recursive: true });
697
+ await ensureEditorPluginBootstrapAnchors(workspace);
698
+ await ensureEditorPluginBuildScriptAnchors(workspace);
699
+ await ensureEditorPluginWebpackAnchors(workspace);
700
+ await fsp.writeFile(entryFilePath, buildEditorPluginEntrySource(editorPluginSlug, workspace.workspace.namespace, workspace.workspace.textDomain), "utf8");
701
+ await fsp.writeFile(sidebarFilePath, buildEditorPluginSidebarSource(editorPluginSlug, workspace.workspace.textDomain), "utf8");
702
+ await fsp.writeFile(dataFilePath, buildEditorPluginDataSource(editorPluginSlug, resolvedSlot), "utf8");
703
+ await fsp.writeFile(typesFilePath, buildEditorPluginTypesSource(editorPluginSlug), "utf8");
704
+ await fsp.writeFile(styleFilePath, buildEditorPluginStyleSource(), "utf8");
705
+ await writeEditorPluginRegistry(workspace.projectDir, editorPluginSlug);
706
+ await appendWorkspaceInventoryEntries(workspace.projectDir, {
707
+ editorPluginEntries: [
708
+ buildEditorPluginConfigEntry(editorPluginSlug, resolvedSlot),
709
+ ],
710
+ });
711
+ return {
712
+ editorPluginSlug,
713
+ projectDir: workspace.projectDir,
714
+ slot: resolvedSlot,
715
+ };
716
+ }
717
+ catch (error) {
718
+ await rollbackWorkspaceMutation(mutationSnapshot);
719
+ throw error;
720
+ }
721
+ }
306
722
  /**
307
723
  * Add one PHP block pattern shell to an official workspace project.
308
724
  *
@@ -0,0 +1,14 @@
1
+ import { type RestResourceMethodId, type RunAddRestResourceCommandOptions } from "./cli-add-shared.js";
2
+ /**
3
+ * Scaffold a workspace-level REST resource and synchronize its generated
4
+ * TypeScript and PHP artifacts.
5
+ *
6
+ * @param options Command options for the REST resource scaffold workflow.
7
+ * @returns Resolved scaffold metadata for the created REST resource.
8
+ */
9
+ export declare function runAddRestResourceCommand({ cwd, methods, namespace, restResourceName, }: RunAddRestResourceCommandOptions): Promise<{
10
+ methods: RestResourceMethodId[];
11
+ namespace: string;
12
+ projectDir: string;
13
+ restResourceSlug: string;
14
+ }>;