@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wp-typia/project-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Project orchestration and programmatic tooling for wp-typia",
|
|
5
5
|
"packageManager": "bun@1.3.11",
|
|
6
6
|
"type": "module",
|
|
@@ -120,8 +120,8 @@
|
|
|
120
120
|
},
|
|
121
121
|
"dependencies": {
|
|
122
122
|
"@wp-typia/api-client": "^0.4.5",
|
|
123
|
-
"@wp-typia/block-runtime": "^0.
|
|
124
|
-
"@wp-typia/rest": "^0.3.
|
|
123
|
+
"@wp-typia/block-runtime": "^0.5.0",
|
|
124
|
+
"@wp-typia/rest": "^0.3.11",
|
|
125
125
|
"@wp-typia/block-types": "^0.2.4",
|
|
126
126
|
"mustache": "^4.2.0",
|
|
127
127
|
"npm-package-arg": "^13.0.0",
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
|
|
5
5
|
const PROJECT_ROOT = process.cwd();
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const CHILD_SPEC_MARKER = '// add-child: insert new child specs here';
|
|
8
8
|
const BLOCK_CONFIG_MARKER = '// add-child: insert new block config entries here';
|
|
9
9
|
const CHILD_PLACEHOLDER = 'Add supporting details for this internal item.';
|
|
10
10
|
const BLOCK_SLUG_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
@@ -16,8 +16,10 @@ type StarterManifestDocument = {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
type CompoundParentConfig = {
|
|
19
|
+
blockJsonPath: string;
|
|
19
20
|
blockName: string;
|
|
20
21
|
namespace: string;
|
|
22
|
+
blocksRoot: string;
|
|
21
23
|
slug: string;
|
|
22
24
|
styleImport: string;
|
|
23
25
|
textDomain: string;
|
|
@@ -25,12 +27,27 @@ type CompoundParentConfig = {
|
|
|
25
27
|
typeName: string;
|
|
26
28
|
};
|
|
27
29
|
|
|
30
|
+
type ExistingCompoundChild = {
|
|
31
|
+
blockJsonPath: string;
|
|
32
|
+
blockName: string;
|
|
33
|
+
folderSlug: string;
|
|
34
|
+
key: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type ParsedArgs = {
|
|
38
|
+
ancestorValues: string[];
|
|
39
|
+
container: boolean;
|
|
40
|
+
inserter?: 'hidden' | 'visible';
|
|
41
|
+
slug?: string;
|
|
42
|
+
title?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
28
45
|
function parseArgs() {
|
|
29
46
|
const args = process.argv.slice( 2 );
|
|
30
|
-
const parsed: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
47
|
+
const parsed: ParsedArgs = {
|
|
48
|
+
ancestorValues: [],
|
|
49
|
+
container: false,
|
|
50
|
+
};
|
|
34
51
|
|
|
35
52
|
for ( let index = 0; index < args.length; index += 1 ) {
|
|
36
53
|
const arg = args[ index ];
|
|
@@ -53,6 +70,31 @@ function parseArgs() {
|
|
|
53
70
|
index += 1;
|
|
54
71
|
continue;
|
|
55
72
|
}
|
|
73
|
+
|
|
74
|
+
if ( arg === '--ancestor' ) {
|
|
75
|
+
const value = args[ index + 1 ];
|
|
76
|
+
if ( ! value || value.startsWith( '--' ) ) {
|
|
77
|
+
throw new Error( '--ancestor requires a value.' );
|
|
78
|
+
}
|
|
79
|
+
parsed.ancestorValues.push( value );
|
|
80
|
+
index += 1;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if ( arg === '--container' ) {
|
|
85
|
+
parsed.container = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if ( arg === '--inserter' ) {
|
|
90
|
+
const value = args[ index + 1 ];
|
|
91
|
+
if ( value !== 'hidden' && value !== 'visible' ) {
|
|
92
|
+
throw new Error( "--inserter must be either 'hidden' or 'visible'." );
|
|
93
|
+
}
|
|
94
|
+
parsed.inserter = value;
|
|
95
|
+
index += 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
56
98
|
}
|
|
57
99
|
|
|
58
100
|
return parsed;
|
|
@@ -177,8 +219,10 @@ function resolveCompoundParentConfig(): CompoundParentConfig {
|
|
|
177
219
|
: parentSlug;
|
|
178
220
|
|
|
179
221
|
return {
|
|
222
|
+
blockJsonPath,
|
|
180
223
|
blockName,
|
|
181
224
|
namespace,
|
|
225
|
+
blocksRoot,
|
|
182
226
|
slug: parentSlug,
|
|
183
227
|
styleImport: `../${ parentSlug }/style.scss`,
|
|
184
228
|
textDomain,
|
|
@@ -188,8 +232,10 @@ function resolveCompoundParentConfig(): CompoundParentConfig {
|
|
|
188
232
|
}
|
|
189
233
|
|
|
190
234
|
const {
|
|
235
|
+
blockJsonPath: PARENT_BLOCK_JSON_PATH,
|
|
191
236
|
blockName: PARENT_BLOCK_NAME,
|
|
192
237
|
namespace: PARENT_BLOCK_NAMESPACE,
|
|
238
|
+
blocksRoot: BLOCKS_ROOT,
|
|
193
239
|
slug: PARENT_BLOCK_SLUG,
|
|
194
240
|
styleImport: PARENT_STYLE_IMPORT,
|
|
195
241
|
textDomain: TEXT_DOMAIN,
|
|
@@ -300,50 +346,263 @@ function insertBeforeMarker( filePath: string, marker: string, insertionLines: s
|
|
|
300
346
|
);
|
|
301
347
|
}
|
|
302
348
|
|
|
349
|
+
function readBlockJsonDocument(
|
|
350
|
+
filePath: string
|
|
351
|
+
): Record< string, unknown > {
|
|
352
|
+
return readJsonFile( filePath );
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function writeBlockJsonDocument(
|
|
356
|
+
filePath: string,
|
|
357
|
+
document: Record< string, unknown >
|
|
358
|
+
) {
|
|
359
|
+
fs.writeFileSync(
|
|
360
|
+
filePath,
|
|
361
|
+
`${ JSON.stringify( document, null, '\t' ) }\n`,
|
|
362
|
+
'utf8'
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function deriveChildKey( folderSlug: string ): string {
|
|
367
|
+
if ( folderSlug.startsWith( `${ PARENT_BLOCK_SLUG }-` ) ) {
|
|
368
|
+
return folderSlug.slice( PARENT_BLOCK_SLUG.length + 1 );
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return resolveValidatedBlockSlug( folderSlug );
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function listExistingCompoundChildren(): ExistingCompoundChild[] {
|
|
375
|
+
if ( ! fs.existsSync( BLOCKS_ROOT ) ) {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return fs
|
|
380
|
+
.readdirSync( BLOCKS_ROOT, { withFileTypes: true } )
|
|
381
|
+
.filter( ( entry ) => entry.isDirectory() )
|
|
382
|
+
.map( ( entry ) => entry.name )
|
|
383
|
+
.filter( ( folderSlug ) => folderSlug !== PARENT_BLOCK_SLUG )
|
|
384
|
+
.filter( ( folderSlug ) => folderSlug.startsWith( `${ PARENT_BLOCK_SLUG }-` ) )
|
|
385
|
+
.map( ( folderSlug ) => {
|
|
386
|
+
const blockJsonPath = path.join( BLOCKS_ROOT, folderSlug, 'block.json' );
|
|
387
|
+
if ( ! fs.existsSync( blockJsonPath ) ) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const blockJson = readBlockJsonDocument( blockJsonPath );
|
|
392
|
+
const blockName =
|
|
393
|
+
typeof blockJson.name === 'string' ? blockJson.name.trim() : '';
|
|
394
|
+
if ( blockName.length === 0 ) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
blockJsonPath,
|
|
400
|
+
blockName,
|
|
401
|
+
folderSlug,
|
|
402
|
+
key: deriveChildKey( folderSlug ),
|
|
403
|
+
} satisfies ExistingCompoundChild;
|
|
404
|
+
} )
|
|
405
|
+
.filter( ( child ): child is ExistingCompoundChild => child !== null );
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function resolveExistingCompoundChild(
|
|
409
|
+
value: string,
|
|
410
|
+
existingChildren: ExistingCompoundChild[]
|
|
411
|
+
): ExistingCompoundChild {
|
|
412
|
+
const trimmedValue = value.trim();
|
|
413
|
+
if ( trimmedValue.length === 0 ) {
|
|
414
|
+
throw new Error( 'Ancestor references must not be empty.' );
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const normalizedCandidate = resolveValidatedBlockSlug(
|
|
418
|
+
trimmedValue.includes( '/' )
|
|
419
|
+
? trimmedValue.slice( trimmedValue.lastIndexOf( '/' ) + 1 )
|
|
420
|
+
: trimmedValue
|
|
421
|
+
);
|
|
422
|
+
const resolved = existingChildren.find(
|
|
423
|
+
( child ) =>
|
|
424
|
+
child.blockName === trimmedValue ||
|
|
425
|
+
child.folderSlug === trimmedValue ||
|
|
426
|
+
child.key === normalizedCandidate ||
|
|
427
|
+
child.folderSlug === `${ PARENT_BLOCK_SLUG }-${ normalizedCandidate }`
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
if ( ! resolved ) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`Unable to resolve compound child ancestor "${ value }". Use an existing child key, folder slug, or block name.`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return resolved;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function ensureUniqueAncestorChain(
|
|
440
|
+
ancestors: ExistingCompoundChild[]
|
|
441
|
+
): ExistingCompoundChild[] {
|
|
442
|
+
const seenKeys = new Set< string >();
|
|
443
|
+
|
|
444
|
+
return ancestors.filter( ( ancestor ) => {
|
|
445
|
+
if ( seenKeys.has( ancestor.key ) ) {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
seenKeys.add( ancestor.key );
|
|
450
|
+
return true;
|
|
451
|
+
} );
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function validateAncestorChain(
|
|
455
|
+
ancestorChain: ExistingCompoundChild[]
|
|
456
|
+
) {
|
|
457
|
+
for ( let index = 0; index < ancestorChain.length - 1; index += 1 ) {
|
|
458
|
+
const currentAncestor = ancestorChain[ index ];
|
|
459
|
+
const nextAncestor = ancestorChain[ index + 1 ];
|
|
460
|
+
const blockJson = readBlockJsonDocument( currentAncestor.blockJsonPath );
|
|
461
|
+
const allowedBlocks = Array.isArray( blockJson.allowedBlocks )
|
|
462
|
+
? blockJson.allowedBlocks.filter(
|
|
463
|
+
( value ): value is string => typeof value === 'string'
|
|
464
|
+
)
|
|
465
|
+
: [];
|
|
466
|
+
|
|
467
|
+
if ( allowedBlocks.includes( nextAncestor.blockName ) ) {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
throw new Error(
|
|
472
|
+
`Invalid ancestor chain: ${ currentAncestor.blockName } does not currently allow ${ nextAncestor.blockName } as a direct child.`
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function findCompoundChildSpecSource(
|
|
478
|
+
childrenRegistrySource: string,
|
|
479
|
+
childKey: string
|
|
480
|
+
): string | null {
|
|
481
|
+
const childSpecPattern = /\{[\s\S]*?key:\s*["'][^"']+["'][\s\S]*?\n\s*\},/g;
|
|
482
|
+
|
|
483
|
+
for ( const match of childrenRegistrySource.matchAll( childSpecPattern ) ) {
|
|
484
|
+
const candidate = match[ 0 ];
|
|
485
|
+
if (
|
|
486
|
+
candidate.includes( `key: "${ childKey }"` ) ||
|
|
487
|
+
candidate.includes( `key: '${ childKey }'` )
|
|
488
|
+
) {
|
|
489
|
+
return candidate;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function validateAncestorInstantiability(
|
|
497
|
+
childrenFile: string,
|
|
498
|
+
ancestorChain: ExistingCompoundChild[]
|
|
499
|
+
) {
|
|
500
|
+
if ( ancestorChain.length === 0 ) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const childrenRegistrySource = fs.readFileSync( childrenFile, 'utf8' );
|
|
505
|
+
|
|
506
|
+
for ( const ancestor of ancestorChain ) {
|
|
507
|
+
const childSpecSource = findCompoundChildSpecSource(
|
|
508
|
+
childrenRegistrySource,
|
|
509
|
+
ancestor.key
|
|
510
|
+
);
|
|
511
|
+
if ( ! childSpecSource ) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const supportsInserterMatch = childSpecSource.match(
|
|
516
|
+
/supportsInserter:\s*(true|false)/
|
|
517
|
+
);
|
|
518
|
+
const supportsInserter = supportsInserterMatch?.[ 1 ] === 'true';
|
|
519
|
+
const hasTemplateInstances = ! /templateInstances:\s*\[\s*\]/.test( childSpecSource );
|
|
520
|
+
|
|
521
|
+
if ( supportsInserter || hasTemplateInstances ) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
throw new Error(
|
|
526
|
+
`Cannot nest descendants under ${ ancestor.blockName } because it is hidden and has no seeded template instances. Re-add or promote that child with a visible inserter or a seeded template first.`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function updateAllowedBlocks(
|
|
532
|
+
filePath: string,
|
|
533
|
+
blockName: string
|
|
534
|
+
) {
|
|
535
|
+
const blockJson = readBlockJsonDocument( filePath );
|
|
536
|
+
const existingAllowedBlocks = Array.isArray( blockJson.allowedBlocks )
|
|
537
|
+
? blockJson.allowedBlocks.filter(
|
|
538
|
+
( value ): value is string => typeof value === 'string'
|
|
539
|
+
)
|
|
540
|
+
: [];
|
|
541
|
+
|
|
542
|
+
if ( existingAllowedBlocks.includes( blockName ) ) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
blockJson.allowedBlocks = [ ...existingAllowedBlocks, blockName ];
|
|
547
|
+
writeBlockJsonDocument( filePath, blockJson );
|
|
548
|
+
}
|
|
549
|
+
|
|
303
550
|
function renderBlockJson(
|
|
304
551
|
childBlockName: string,
|
|
305
552
|
childFolderSlug: string,
|
|
306
|
-
childTitle: string
|
|
553
|
+
childTitle: string,
|
|
554
|
+
options: {
|
|
555
|
+
allowedBlocks?: string[];
|
|
556
|
+
ancestorBlockNames: string[];
|
|
557
|
+
container: boolean;
|
|
558
|
+
supportsInserter: boolean;
|
|
559
|
+
}
|
|
307
560
|
): string {
|
|
308
561
|
const childCssClassName = buildBlockCssClassName( PARENT_BLOCK_NAMESPACE, childFolderSlug );
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
562
|
+
const document: Record< string, unknown > = {
|
|
563
|
+
$schema: 'https://schemas.wp.org/trunk/block.json',
|
|
564
|
+
apiVersion: 3,
|
|
565
|
+
name: childBlockName,
|
|
566
|
+
version: '{{blockMetadataVersion}}',
|
|
567
|
+
title: childTitle,
|
|
568
|
+
category: '{{compoundChildCategory}}',
|
|
569
|
+
icon: '{{compoundChildIcon}}',
|
|
570
|
+
description: `Internal item block used by ${ PARENT_BLOCK_TITLE }.`,
|
|
571
|
+
example: {},
|
|
572
|
+
supports: {
|
|
573
|
+
html: false,
|
|
574
|
+
inserter: options.supportsInserter,
|
|
575
|
+
reusable: false,
|
|
576
|
+
},
|
|
577
|
+
attributes: {
|
|
578
|
+
title: {
|
|
579
|
+
type: 'string',
|
|
580
|
+
source: 'html',
|
|
581
|
+
selector: `.${ childCssClassName }__title`,
|
|
582
|
+
default: childTitle,
|
|
326
583
|
},
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
default: childTitle,
|
|
333
|
-
},
|
|
334
|
-
body: {
|
|
335
|
-
type: 'string',
|
|
336
|
-
source: 'html',
|
|
337
|
-
selector: `.${ childCssClassName }__body`,
|
|
338
|
-
default: CHILD_PLACEHOLDER,
|
|
339
|
-
},
|
|
584
|
+
body: {
|
|
585
|
+
type: 'string',
|
|
586
|
+
source: 'html',
|
|
587
|
+
selector: `.${ childCssClassName }__body`,
|
|
588
|
+
default: CHILD_PLACEHOLDER,
|
|
340
589
|
},
|
|
341
|
-
textdomain: TEXT_DOMAIN,
|
|
342
|
-
editorScript: 'file:./index.js',
|
|
343
590
|
},
|
|
344
|
-
|
|
345
|
-
'
|
|
346
|
-
|
|
591
|
+
textdomain: TEXT_DOMAIN,
|
|
592
|
+
editorScript: 'file:./index.js',
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
if ( options.ancestorBlockNames.length > 0 ) {
|
|
596
|
+
document.ancestor = options.ancestorBlockNames;
|
|
597
|
+
} else {
|
|
598
|
+
document.parent = [ PARENT_BLOCK_NAME ];
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if ( options.container || ( options.allowedBlocks && options.allowedBlocks.length > 0 ) ) {
|
|
602
|
+
document.allowedBlocks = options.allowedBlocks ?? [];
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return `${ JSON.stringify( document, null, '\t' ) }\n`;
|
|
347
606
|
}
|
|
348
607
|
|
|
349
608
|
function renderTypesFile(
|
|
@@ -491,10 +750,16 @@ function renderEditFile(
|
|
|
491
750
|
const childCssClassName = buildBlockCssClassName( PARENT_BLOCK_NAMESPACE, childFolderSlug );
|
|
492
751
|
|
|
493
752
|
return `import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';
|
|
494
|
-
import { RichText, useBlockProps } from '@wordpress/block-editor';
|
|
753
|
+
import { InnerBlocks, RichText, useBlockProps } from '@wordpress/block-editor';
|
|
495
754
|
import { Notice } from '@wordpress/components';
|
|
496
755
|
import { __ } from '@wordpress/i18n';
|
|
497
756
|
|
|
757
|
+
import metadata from './block-metadata';
|
|
758
|
+
import {
|
|
759
|
+
\tgetChildAllowedBlocks,
|
|
760
|
+
\tgetChildTemplate,
|
|
761
|
+
\thasNestedChildBlocks,
|
|
762
|
+
} from '../${ PARENT_BLOCK_SLUG }/children';
|
|
498
763
|
import { useTypiaValidation } from './hooks';
|
|
499
764
|
import type { ${ childTypeName } } from './types';
|
|
500
765
|
import {
|
|
@@ -513,6 +778,9 @@ export default function Edit( {
|
|
|
513
778
|
\t\tattributes,
|
|
514
779
|
\t\tvalidate${ childInterfaceName }
|
|
515
780
|
\t);
|
|
781
|
+
\tconst nestedAllowedBlocks = getChildAllowedBlocks( metadata.name );
|
|
782
|
+
\tconst nestedTemplate = getChildTemplate( metadata.name );
|
|
783
|
+
\tconst showsNestedChildren = hasNestedChildBlocks( metadata.name );
|
|
516
784
|
|
|
517
785
|
\treturn (
|
|
518
786
|
\t\t<div { ...useBlockProps( { className: '${ childCssClassName }' } ) }>
|
|
@@ -539,6 +807,16 @@ export default function Edit( {
|
|
|
539
807
|
\t\t\t\t\t</ul>
|
|
540
808
|
\t\t\t\t</Notice>
|
|
541
809
|
\t\t\t) }
|
|
810
|
+
\t\t\t{ showsNestedChildren && (
|
|
811
|
+
\t\t\t\t<div className="${ childCssClassName }__children">
|
|
812
|
+
\t\t\t\t\t<InnerBlocks
|
|
813
|
+
\t\t\t\t\t\tallowedBlocks={ nestedAllowedBlocks }
|
|
814
|
+
\t\t\t\t\t\trenderAppender={ InnerBlocks.ButtonBlockAppender }
|
|
815
|
+
\t\t\t\t\t\ttemplate={ nestedTemplate }
|
|
816
|
+
\t\t\t\t\t\ttemplateLock={ false }
|
|
817
|
+
\t\t\t\t\t/>
|
|
818
|
+
\t\t\t\t</div>
|
|
819
|
+
\t\t\t) }
|
|
542
820
|
\t\t</div>
|
|
543
821
|
\t);
|
|
544
822
|
}
|
|
@@ -548,8 +826,10 @@ export default function Edit( {
|
|
|
548
826
|
function renderSaveFile( childFolderSlug: string, childTypeName: string ): string {
|
|
549
827
|
const childCssClassName = buildBlockCssClassName( PARENT_BLOCK_NAMESPACE, childFolderSlug );
|
|
550
828
|
|
|
551
|
-
return `import { RichText, useBlockProps } from '@wordpress/block-editor';
|
|
829
|
+
return `import { InnerBlocks, RichText, useBlockProps } from '@wordpress/block-editor';
|
|
552
830
|
|
|
831
|
+
import metadata from './block-metadata';
|
|
832
|
+
import { hasNestedChildBlocks } from '../${ PARENT_BLOCK_SLUG }/children';
|
|
553
833
|
import type { ${ childTypeName } } from './types';
|
|
554
834
|
|
|
555
835
|
export default function Save( {
|
|
@@ -557,6 +837,8 @@ export default function Save( {
|
|
|
557
837
|
}: {
|
|
558
838
|
\tattributes: ${ childTypeName };
|
|
559
839
|
} ) {
|
|
840
|
+
\tconst showsNestedChildren = hasNestedChildBlocks( metadata.name );
|
|
841
|
+
|
|
560
842
|
\treturn (
|
|
561
843
|
\t\t<div { ...useBlockProps.save( { className: '${ childCssClassName }' } ) }>
|
|
562
844
|
\t\t\t<RichText.Content
|
|
@@ -569,13 +851,18 @@ export default function Save( {
|
|
|
569
851
|
\t\t\t\tclassName="${ childCssClassName }__body"
|
|
570
852
|
\t\t\t\tvalue={ attributes.body }
|
|
571
853
|
\t\t\t/>
|
|
854
|
+
\t\t\t{ showsNestedChildren && (
|
|
855
|
+
\t\t\t\t<div className="${ childCssClassName }__children">
|
|
856
|
+
\t\t\t\t\t<InnerBlocks.Content />
|
|
857
|
+
\t\t\t\t</div>
|
|
858
|
+
\t\t\t) }
|
|
572
859
|
\t\t</div>
|
|
573
860
|
\t);
|
|
574
861
|
}
|
|
575
862
|
`;
|
|
576
863
|
}
|
|
577
864
|
|
|
578
|
-
function renderIndexFile( childTypeName: string
|
|
865
|
+
function renderIndexFile( childTypeName: string ): string {
|
|
579
866
|
return `import {
|
|
580
867
|
\tregisterScaffoldBlockType,
|
|
581
868
|
\ttype BlockConfiguration,
|
|
@@ -604,14 +891,68 @@ registerScaffoldBlockType( registration.name, registration.settings );
|
|
|
604
891
|
`;
|
|
605
892
|
}
|
|
606
893
|
|
|
894
|
+
function renderChildSpecLines(
|
|
895
|
+
options: {
|
|
896
|
+
ancestorKeys: string[];
|
|
897
|
+
blockName: string;
|
|
898
|
+
container: boolean;
|
|
899
|
+
folderSlug: string;
|
|
900
|
+
key: string;
|
|
901
|
+
placement: 'nested' | 'root';
|
|
902
|
+
seedTemplate: boolean;
|
|
903
|
+
supportsInserter: boolean;
|
|
904
|
+
title: string;
|
|
905
|
+
}
|
|
906
|
+
): string[] {
|
|
907
|
+
const templateLines =
|
|
908
|
+
options.seedTemplate
|
|
909
|
+
? [
|
|
910
|
+
'\ttemplateInstances: [',
|
|
911
|
+
'\t\t{',
|
|
912
|
+
`\t\t\tbody: ${ JSON.stringify( CHILD_PLACEHOLDER ) },`,
|
|
913
|
+
`\t\t\ttitle: ${ JSON.stringify( options.title ) },`,
|
|
914
|
+
'\t\t},',
|
|
915
|
+
'\t],',
|
|
916
|
+
]
|
|
917
|
+
: [ '\ttemplateInstances: [],' ];
|
|
918
|
+
|
|
919
|
+
return [
|
|
920
|
+
'{',
|
|
921
|
+
`\tancestorKeys: [ ${ options.ancestorKeys.map( ( value ) => JSON.stringify( value ) ).join( ', ' ) } ],`,
|
|
922
|
+
`\tblockName: ${ JSON.stringify( options.blockName ) },`,
|
|
923
|
+
`\tbodyPlaceholder: ${ JSON.stringify( CHILD_PLACEHOLDER ) },`,
|
|
924
|
+
`\tcontainer: ${ options.container ? 'true' : 'false' },`,
|
|
925
|
+
`\tfolderSlug: ${ JSON.stringify( options.folderSlug ) },`,
|
|
926
|
+
`\tkey: ${ JSON.stringify( options.key ) },`,
|
|
927
|
+
`\tplacement: ${ JSON.stringify( options.placement ) },`,
|
|
928
|
+
`\tsupportsInserter: ${ options.supportsInserter ? 'true' : 'false' },`,
|
|
929
|
+
...templateLines,
|
|
930
|
+
`\ttitle: ${ JSON.stringify( options.title ) },`,
|
|
931
|
+
'},',
|
|
932
|
+
];
|
|
933
|
+
}
|
|
934
|
+
|
|
607
935
|
function main() {
|
|
608
|
-
const {
|
|
936
|
+
const {
|
|
937
|
+
ancestorValues,
|
|
938
|
+
container,
|
|
939
|
+
inserter,
|
|
940
|
+
slug,
|
|
941
|
+
title,
|
|
942
|
+
} = parseArgs();
|
|
609
943
|
const normalizedSlug = slug ? resolveValidatedBlockSlug( slug ) : '';
|
|
610
944
|
|
|
611
945
|
if ( normalizedSlug.length === 0 ) {
|
|
612
946
|
throw new Error( 'Use a child slug with lowercase letters, numbers, and hyphens only.' );
|
|
613
947
|
}
|
|
614
948
|
|
|
949
|
+
const existingChildren = listExistingCompoundChildren();
|
|
950
|
+
const ancestorChain = ensureUniqueAncestorChain(
|
|
951
|
+
ancestorValues.map( ( value ) =>
|
|
952
|
+
resolveExistingCompoundChild( value, existingChildren )
|
|
953
|
+
)
|
|
954
|
+
);
|
|
955
|
+
validateAncestorChain( ancestorChain );
|
|
615
956
|
const childTitle = title?.trim().length ? title.trim() : toTitleCase( normalizedSlug );
|
|
616
957
|
const childFolderSlug = `${ PARENT_BLOCK_SLUG }-${ normalizedSlug }`;
|
|
617
958
|
const childBlockName = `${ PARENT_BLOCK_NAME }-${ normalizedSlug }`;
|
|
@@ -628,6 +969,10 @@ function main() {
|
|
|
628
969
|
);
|
|
629
970
|
const blockConfigFile = path.join( PROJECT_ROOT, 'scripts', 'block-config.ts' );
|
|
630
971
|
|
|
972
|
+
if ( ancestorChain.some( ( ancestor ) => ancestor.key === normalizedSlug ) ) {
|
|
973
|
+
throw new Error( 'A child block cannot list itself as an ancestor.' );
|
|
974
|
+
}
|
|
975
|
+
|
|
631
976
|
if ( fs.existsSync( childDir ) ) {
|
|
632
977
|
throw new Error( `Child block already exists: ${ childFolderSlug }` );
|
|
633
978
|
}
|
|
@@ -638,11 +983,28 @@ function main() {
|
|
|
638
983
|
);
|
|
639
984
|
}
|
|
640
985
|
|
|
986
|
+
validateAncestorInstantiability( childrenFile, ancestorChain );
|
|
987
|
+
|
|
988
|
+
const supportsInserter =
|
|
989
|
+
inserter === 'visible'
|
|
990
|
+
? true
|
|
991
|
+
: inserter === 'hidden'
|
|
992
|
+
? false
|
|
993
|
+
: container || ancestorChain.length > 0;
|
|
994
|
+
const placement = ancestorChain.length > 0 ? 'nested' : 'root';
|
|
995
|
+
const seedTemplate = supportsInserter || container || ancestorChain.length > 0;
|
|
996
|
+
const directAllowedBlocks: string[] = [];
|
|
997
|
+
|
|
641
998
|
fs.mkdirSync( childDir, { recursive: true } );
|
|
642
999
|
|
|
643
1000
|
fs.writeFileSync(
|
|
644
1001
|
path.join( childDir, 'block.json' ),
|
|
645
|
-
renderBlockJson( childBlockName, childFolderSlug, childTitle
|
|
1002
|
+
renderBlockJson( childBlockName, childFolderSlug, childTitle, {
|
|
1003
|
+
allowedBlocks: directAllowedBlocks,
|
|
1004
|
+
ancestorBlockNames: ancestorChain.map( ( ancestor ) => ancestor.blockName ),
|
|
1005
|
+
container,
|
|
1006
|
+
supportsInserter,
|
|
1007
|
+
} ),
|
|
646
1008
|
'utf8'
|
|
647
1009
|
);
|
|
648
1010
|
fs.writeFileSync(
|
|
@@ -688,14 +1050,24 @@ function main() {
|
|
|
688
1050
|
);
|
|
689
1051
|
fs.writeFileSync(
|
|
690
1052
|
path.join( childDir, 'index.tsx' ),
|
|
691
|
-
renderIndexFile( childTypeName
|
|
1053
|
+
renderIndexFile( childTypeName ),
|
|
692
1054
|
'utf8'
|
|
693
1055
|
);
|
|
694
1056
|
|
|
695
1057
|
insertBeforeMarker(
|
|
696
1058
|
childrenFile,
|
|
697
|
-
|
|
698
|
-
|
|
1059
|
+
CHILD_SPEC_MARKER,
|
|
1060
|
+
renderChildSpecLines( {
|
|
1061
|
+
ancestorKeys: ancestorChain.map( ( ancestor ) => ancestor.key ),
|
|
1062
|
+
blockName: childBlockName,
|
|
1063
|
+
container,
|
|
1064
|
+
folderSlug: childFolderSlug,
|
|
1065
|
+
key: normalizedSlug,
|
|
1066
|
+
placement,
|
|
1067
|
+
seedTemplate,
|
|
1068
|
+
supportsInserter,
|
|
1069
|
+
title: childTitle,
|
|
1070
|
+
} )
|
|
699
1071
|
);
|
|
700
1072
|
insertBeforeMarker(
|
|
701
1073
|
blockConfigFile,
|
|
@@ -709,6 +1081,13 @@ function main() {
|
|
|
709
1081
|
]
|
|
710
1082
|
);
|
|
711
1083
|
|
|
1084
|
+
if ( placement === 'root' ) {
|
|
1085
|
+
updateAllowedBlocks( PARENT_BLOCK_JSON_PATH, childBlockName );
|
|
1086
|
+
} else {
|
|
1087
|
+
const directAncestor = ancestorChain[ ancestorChain.length - 1 ];
|
|
1088
|
+
updateAllowedBlocks( directAncestor.blockJsonPath, childBlockName );
|
|
1089
|
+
}
|
|
1090
|
+
|
|
712
1091
|
console.log( `✅ Added compound child block ${ childBlockName }` );
|
|
713
1092
|
console.log(
|
|
714
1093
|
'Run `sync-types` next to generate block.json metadata, manifests, schemas, and PHP validators for the new child block.'
|