@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.
- package/dist/runtime/block-generator-service-core.d.ts +1 -1
- package/dist/runtime/block-generator-service-core.js +2 -1
- package/dist/runtime/block-generator-service-spec.d.ts +8 -1
- package/dist/runtime/block-generator-service-spec.js +27 -0
- package/dist/runtime/built-in-block-artifacts.js +1 -0
- package/dist/runtime/built-in-block-code-artifacts.js +14 -1
- package/dist/runtime/built-in-block-code-templates/compound-child.d.ts +2 -2
- package/dist/runtime/built-in-block-code-templates/compound-child.js +30 -2
- package/dist/runtime/built-in-block-code-templates/compound-parent.d.ts +1 -1
- package/dist/runtime/built-in-block-code-templates/compound-parent.js +139 -19
- package/dist/runtime/built-in-block-code-templates/query-loop.d.ts +1 -0
- package/dist/runtime/built-in-block-code-templates/query-loop.js +70 -0
- package/dist/runtime/built-in-block-code-templates.d.ts +1 -0
- package/dist/runtime/built-in-block-code-templates.js +1 -0
- package/dist/runtime/built-in-block-non-ts-artifacts.js +2 -0
- package/dist/runtime/cli-add-block.d.ts +2 -1
- package/dist/runtime/cli-add-block.js +19 -1
- package/dist/runtime/cli-add-shared.d.ts +55 -1
- package/dist/runtime/cli-add-shared.js +101 -2
- package/dist/runtime/cli-add-workspace-assets.d.ts +21 -1
- package/dist/runtime/cli-add-workspace-assets.js +417 -1
- package/dist/runtime/cli-add-workspace-rest.d.ts +14 -0
- package/dist/runtime/cli-add-workspace-rest.js +1060 -0
- package/dist/runtime/cli-add-workspace.d.ts +10 -1
- package/dist/runtime/cli-add-workspace.js +10 -1
- package/dist/runtime/cli-add.d.ts +3 -3
- package/dist/runtime/cli-add.js +2 -2
- package/dist/runtime/cli-core.d.ts +3 -1
- package/dist/runtime/cli-core.js +2 -1
- package/dist/runtime/cli-doctor-workspace.js +135 -1
- package/dist/runtime/cli-help.js +10 -5
- package/dist/runtime/cli-scaffold.d.ts +11 -2
- package/dist/runtime/cli-scaffold.js +137 -36
- package/dist/runtime/cli-templates.d.ts +4 -4
- package/dist/runtime/cli-templates.js +79 -39
- package/dist/runtime/index.d.ts +4 -3
- package/dist/runtime/index.js +3 -2
- package/dist/runtime/local-dev-presets.js +27 -12
- package/dist/runtime/rest-resource-artifacts.d.ts +35 -0
- package/dist/runtime/rest-resource-artifacts.js +158 -0
- package/dist/runtime/scaffold-answer-resolution.d.ts +1 -1
- package/dist/runtime/scaffold-answer-resolution.js +94 -3
- package/dist/runtime/scaffold-apply-utils.d.ts +4 -3
- package/dist/runtime/scaffold-apply-utils.js +34 -17
- package/dist/runtime/scaffold-bootstrap.d.ts +15 -0
- package/dist/runtime/scaffold-bootstrap.js +29 -7
- package/dist/runtime/scaffold-documents.js +9 -9
- package/dist/runtime/scaffold-onboarding.js +17 -1
- package/dist/runtime/scaffold-package-manager-files.js +6 -1
- package/dist/runtime/scaffold-template-variables.js +6 -0
- package/dist/runtime/scaffold.d.ts +15 -1
- package/dist/runtime/scaffold.js +50 -8
- package/dist/runtime/template-defaults.d.ts +7 -0
- package/dist/runtime/template-defaults.js +4 -0
- package/dist/runtime/template-registry.d.ts +1 -1
- package/dist/runtime/template-registry.js +14 -1
- package/dist/runtime/template-render.d.ts +5 -2
- package/dist/runtime/template-render.js +9 -3
- package/dist/runtime/template-source-contracts.d.ts +11 -0
- package/dist/runtime/template-source-external.d.ts +1 -1
- package/dist/runtime/template-source-external.js +45 -13
- package/dist/runtime/template-source-normalization.d.ts +1 -1
- package/dist/runtime/template-source-normalization.js +5 -1
- package/dist/runtime/template-source-remote.d.ts +5 -0
- package/dist/runtime/template-source-remote.js +33 -0
- package/dist/runtime/template-source.js +30 -1
- package/dist/runtime/workspace-inventory.d.ts +43 -1
- package/dist/runtime/workspace-inventory.js +132 -1
- package/dist/runtime/workspace-project.d.ts +1 -1
- package/dist/runtime/workspace-project.js +2 -2
- package/package.json +3 -3
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +428 -49
- package/templates/query-loop/inc/query-runtime.php.mustache +85 -0
- package/templates/query-loop/package.json.mustache +32 -0
- package/templates/query-loop/src/patterns/grid.php.mustache +49 -0
- package/templates/query-loop/src/patterns/list.php.mustache +48 -0
- package/templates/query-loop/src/query-extension.ts.mustache +41 -0
- package/templates/query-loop/webpack.config.js.mustache +16 -0
- 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
|
+
}>;
|