@wp-typia/project-tools 0.17.0 → 0.19.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/alternate-render-targets.d.ts +5 -0
- package/dist/runtime/alternate-render-targets.js +29 -0
- package/dist/runtime/block-generator-service-core.d.ts +2 -2
- package/dist/runtime/block-generator-service-core.js +13 -8
- package/dist/runtime/block-generator-service-spec.d.ts +10 -2
- package/dist/runtime/block-generator-service-spec.js +43 -1
- package/dist/runtime/built-in-block-artifacts.js +1 -0
- 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 +35 -2
- package/dist/runtime/built-in-block-code-templates/compound-parent.d.ts +2 -2
- package/dist/runtime/built-in-block-code-templates/compound-parent.js +204 -27
- package/dist/runtime/built-in-block-code-templates/compound-persistence.d.ts +1 -1
- package/dist/runtime/built-in-block-code-templates/compound-persistence.js +11 -8
- package/dist/runtime/built-in-block-non-ts-artifacts.js +505 -2
- package/dist/runtime/cli-add-block.d.ts +6 -2
- package/dist/runtime/cli-add-block.js +71 -24
- package/dist/runtime/cli-add-shared.d.ts +58 -2
- package/dist/runtime/cli-add-shared.js +111 -12
- 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 +5 -1
- package/dist/runtime/cli-core.js +3 -1
- package/dist/runtime/cli-doctor-workspace.js +135 -1
- package/dist/runtime/cli-help.js +12 -7
- package/dist/runtime/cli-scaffold.d.ts +12 -2
- package/dist/runtime/cli-scaffold.js +222 -46
- package/dist/runtime/cli-templates.d.ts +4 -4
- package/dist/runtime/cli-templates.js +104 -39
- package/dist/runtime/cli-validation.d.ts +66 -0
- package/dist/runtime/cli-validation.js +92 -0
- package/dist/runtime/compound-inner-blocks.d.ts +78 -0
- package/dist/runtime/compound-inner-blocks.js +88 -0
- package/dist/runtime/index.d.ts +6 -3
- package/dist/runtime/index.js +4 -2
- package/dist/runtime/local-dev-presets.js +7 -2
- package/dist/runtime/migration-command-surface.js +2 -0
- package/dist/runtime/package-versions.d.ts +1 -0
- package/dist/runtime/package-versions.js +12 -0
- 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.js +78 -8
- 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 +24 -3
- package/dist/runtime/scaffold-identifiers.d.ts +17 -0
- package/dist/runtime/scaffold-identifiers.js +22 -0
- package/dist/runtime/scaffold-onboarding.js +25 -13
- package/dist/runtime/scaffold-package-manager-files.js +6 -1
- package/dist/runtime/scaffold-template-variables.js +22 -0
- package/dist/runtime/scaffold.d.ts +22 -1
- package/dist/runtime/scaffold.js +56 -11
- 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 +35 -4
- 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 +3 -3
- package/package.json +9 -4
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +728 -49
- package/templates/query-loop/src/validator-toolkit.ts.mustache +0 -1
|
@@ -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,47 @@ type CompoundParentConfig = {
|
|
|
25
27
|
typeName: string;
|
|
26
28
|
};
|
|
27
29
|
|
|
30
|
+
type ExistingCompoundChild = {
|
|
31
|
+
ancestorBlockNames: string[];
|
|
32
|
+
blockJsonPath: string;
|
|
33
|
+
blockName: string;
|
|
34
|
+
container: boolean;
|
|
35
|
+
directAllowedBlocks: string[];
|
|
36
|
+
folderSlug: string;
|
|
37
|
+
key: string;
|
|
38
|
+
parentBlockNames: string[];
|
|
39
|
+
placement: 'nested' | 'root';
|
|
40
|
+
supportsInserter: boolean;
|
|
41
|
+
title: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type ParsedArgs = {
|
|
45
|
+
ancestorValues: string[];
|
|
46
|
+
container: boolean;
|
|
47
|
+
dryRun: boolean;
|
|
48
|
+
inserter?: 'hidden' | 'visible';
|
|
49
|
+
slug?: string;
|
|
50
|
+
title?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type CompoundChildGraphNode = {
|
|
54
|
+
blockName: string;
|
|
55
|
+
container: boolean;
|
|
56
|
+
directParentBlockName: string;
|
|
57
|
+
isProspective: boolean;
|
|
58
|
+
key: string;
|
|
59
|
+
placement: 'nested' | 'root';
|
|
60
|
+
supportsInserter: boolean;
|
|
61
|
+
title: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
28
64
|
function parseArgs() {
|
|
29
65
|
const args = process.argv.slice( 2 );
|
|
30
|
-
const parsed: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
66
|
+
const parsed: ParsedArgs = {
|
|
67
|
+
ancestorValues: [],
|
|
68
|
+
container: false,
|
|
69
|
+
dryRun: false,
|
|
70
|
+
};
|
|
34
71
|
|
|
35
72
|
for ( let index = 0; index < args.length; index += 1 ) {
|
|
36
73
|
const arg = args[ index ];
|
|
@@ -53,6 +90,40 @@ function parseArgs() {
|
|
|
53
90
|
index += 1;
|
|
54
91
|
continue;
|
|
55
92
|
}
|
|
93
|
+
|
|
94
|
+
if ( arg === '--ancestor' ) {
|
|
95
|
+
const value = args[ index + 1 ];
|
|
96
|
+
if ( ! value || value.startsWith( '--' ) ) {
|
|
97
|
+
throw new Error( '--ancestor requires a value.' );
|
|
98
|
+
}
|
|
99
|
+
parsed.ancestorValues.push( value );
|
|
100
|
+
index += 1;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if ( arg === '--container' ) {
|
|
105
|
+
parsed.container = true;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if ( arg === '--dry-run' ) {
|
|
110
|
+
parsed.dryRun = true;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if ( arg === '--inserter' ) {
|
|
115
|
+
const value = args[ index + 1 ];
|
|
116
|
+
if ( value !== 'hidden' && value !== 'visible' ) {
|
|
117
|
+
throw new Error( "--inserter must be either 'hidden' or 'visible'." );
|
|
118
|
+
}
|
|
119
|
+
parsed.inserter = value;
|
|
120
|
+
index += 1;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if ( arg.startsWith( '--' ) ) {
|
|
125
|
+
throw new Error( `Unknown option: ${ arg }.` );
|
|
126
|
+
}
|
|
56
127
|
}
|
|
57
128
|
|
|
58
129
|
return parsed;
|
|
@@ -177,8 +248,10 @@ function resolveCompoundParentConfig(): CompoundParentConfig {
|
|
|
177
248
|
: parentSlug;
|
|
178
249
|
|
|
179
250
|
return {
|
|
251
|
+
blockJsonPath,
|
|
180
252
|
blockName,
|
|
181
253
|
namespace,
|
|
254
|
+
blocksRoot,
|
|
182
255
|
slug: parentSlug,
|
|
183
256
|
styleImport: `../${ parentSlug }/style.scss`,
|
|
184
257
|
textDomain,
|
|
@@ -188,8 +261,10 @@ function resolveCompoundParentConfig(): CompoundParentConfig {
|
|
|
188
261
|
}
|
|
189
262
|
|
|
190
263
|
const {
|
|
264
|
+
blockJsonPath: PARENT_BLOCK_JSON_PATH,
|
|
191
265
|
blockName: PARENT_BLOCK_NAME,
|
|
192
266
|
namespace: PARENT_BLOCK_NAMESPACE,
|
|
267
|
+
blocksRoot: BLOCKS_ROOT,
|
|
193
268
|
slug: PARENT_BLOCK_SLUG,
|
|
194
269
|
styleImport: PARENT_STYLE_IMPORT,
|
|
195
270
|
textDomain: TEXT_DOMAIN,
|
|
@@ -300,50 +375,486 @@ function insertBeforeMarker( filePath: string, marker: string, insertionLines: s
|
|
|
300
375
|
);
|
|
301
376
|
}
|
|
302
377
|
|
|
378
|
+
function readBlockJsonDocument(
|
|
379
|
+
filePath: string
|
|
380
|
+
): Record< string, unknown > {
|
|
381
|
+
return readJsonFile( filePath );
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function writeBlockJsonDocument(
|
|
385
|
+
filePath: string,
|
|
386
|
+
document: Record< string, unknown >
|
|
387
|
+
) {
|
|
388
|
+
fs.writeFileSync(
|
|
389
|
+
filePath,
|
|
390
|
+
`${ JSON.stringify( document, null, '\t' ) }\n`,
|
|
391
|
+
'utf8'
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function deriveChildKey( folderSlug: string ): string {
|
|
396
|
+
if ( folderSlug.startsWith( `${ PARENT_BLOCK_SLUG }-` ) ) {
|
|
397
|
+
return folderSlug.slice( PARENT_BLOCK_SLUG.length + 1 );
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return resolveValidatedBlockSlug( folderSlug );
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function listExistingCompoundChildren(): ExistingCompoundChild[] {
|
|
404
|
+
if ( ! fs.existsSync( BLOCKS_ROOT ) ) {
|
|
405
|
+
return [];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return fs
|
|
409
|
+
.readdirSync( BLOCKS_ROOT, { withFileTypes: true } )
|
|
410
|
+
.filter( ( entry ) => entry.isDirectory() )
|
|
411
|
+
.map( ( entry ) => entry.name )
|
|
412
|
+
.filter( ( folderSlug ) => folderSlug !== PARENT_BLOCK_SLUG )
|
|
413
|
+
.filter( ( folderSlug ) => folderSlug.startsWith( `${ PARENT_BLOCK_SLUG }-` ) )
|
|
414
|
+
.map( ( folderSlug ) => {
|
|
415
|
+
const blockJsonPath = path.join( BLOCKS_ROOT, folderSlug, 'block.json' );
|
|
416
|
+
if ( ! fs.existsSync( blockJsonPath ) ) {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const blockJson = readBlockJsonDocument( blockJsonPath );
|
|
421
|
+
const blockName =
|
|
422
|
+
typeof blockJson.name === 'string' ? blockJson.name.trim() : '';
|
|
423
|
+
if ( blockName.length === 0 ) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const ancestorBlockNames = Array.isArray( blockJson.ancestor )
|
|
428
|
+
? blockJson.ancestor.filter(
|
|
429
|
+
( value ): value is string => typeof value === 'string'
|
|
430
|
+
)
|
|
431
|
+
: [];
|
|
432
|
+
const parentBlockNames = Array.isArray( blockJson.parent )
|
|
433
|
+
? blockJson.parent.filter(
|
|
434
|
+
( value ): value is string => typeof value === 'string'
|
|
435
|
+
)
|
|
436
|
+
: [];
|
|
437
|
+
const directAllowedBlocks = Array.isArray( blockJson.allowedBlocks )
|
|
438
|
+
? blockJson.allowedBlocks.filter(
|
|
439
|
+
( value ): value is string => typeof value === 'string'
|
|
440
|
+
)
|
|
441
|
+
: [];
|
|
442
|
+
const supports =
|
|
443
|
+
typeof blockJson.supports === 'object' &&
|
|
444
|
+
blockJson.supports &&
|
|
445
|
+
! Array.isArray( blockJson.supports )
|
|
446
|
+
? blockJson.supports
|
|
447
|
+
: null;
|
|
448
|
+
const supportsInserter =
|
|
449
|
+
supports && typeof supports.inserter === 'boolean'
|
|
450
|
+
? supports.inserter
|
|
451
|
+
: true;
|
|
452
|
+
const title =
|
|
453
|
+
typeof blockJson.title === 'string' &&
|
|
454
|
+
blockJson.title.trim().length > 0
|
|
455
|
+
? blockJson.title.trim()
|
|
456
|
+
: toTitleCase( deriveChildKey( folderSlug ) );
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
ancestorBlockNames,
|
|
460
|
+
blockJsonPath,
|
|
461
|
+
blockName,
|
|
462
|
+
container: Object.prototype.hasOwnProperty.call(
|
|
463
|
+
blockJson,
|
|
464
|
+
'allowedBlocks'
|
|
465
|
+
),
|
|
466
|
+
directAllowedBlocks,
|
|
467
|
+
folderSlug,
|
|
468
|
+
key: deriveChildKey( folderSlug ),
|
|
469
|
+
parentBlockNames,
|
|
470
|
+
placement: ancestorBlockNames.length > 0 ? 'nested' : 'root',
|
|
471
|
+
supportsInserter,
|
|
472
|
+
title,
|
|
473
|
+
} satisfies ExistingCompoundChild;
|
|
474
|
+
} )
|
|
475
|
+
.filter( ( child ): child is ExistingCompoundChild => child !== null );
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function buildCompoundChildGraphMap(
|
|
479
|
+
existingChildren: ExistingCompoundChild[]
|
|
480
|
+
): Map< string, ExistingCompoundChild > {
|
|
481
|
+
return new Map(
|
|
482
|
+
existingChildren.map( ( child ) => [ child.blockName, child ] )
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function resolveDirectParentBlockName( child: ExistingCompoundChild ): string {
|
|
487
|
+
return (
|
|
488
|
+
child.ancestorBlockNames[ child.ancestorBlockNames.length - 1 ] ??
|
|
489
|
+
child.parentBlockNames[ 0 ] ??
|
|
490
|
+
PARENT_BLOCK_NAME
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function validateExistingCompoundChildGraph(
|
|
495
|
+
existingChildren: ExistingCompoundChild[]
|
|
496
|
+
) {
|
|
497
|
+
const seenKeys = new Set< string >();
|
|
498
|
+
const seenBlockNames = new Set< string >();
|
|
499
|
+
const childByBlockName = buildCompoundChildGraphMap( existingChildren );
|
|
500
|
+
|
|
501
|
+
for ( const child of existingChildren ) {
|
|
502
|
+
if ( seenKeys.has( child.key ) ) {
|
|
503
|
+
throw new Error(
|
|
504
|
+
`Existing compound child graph is invalid: child key "${ child.key }" is declared more than once.`
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if ( seenBlockNames.has( child.blockName ) ) {
|
|
509
|
+
throw new Error(
|
|
510
|
+
`Existing compound child graph is invalid: ${ child.blockName } is declared more than once.`
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
seenKeys.add( child.key );
|
|
515
|
+
seenBlockNames.add( child.blockName );
|
|
516
|
+
|
|
517
|
+
if ( child.ancestorBlockNames.length === 0 ) {
|
|
518
|
+
if ( child.parentBlockNames.length === 0 ) {
|
|
519
|
+
throw new Error(
|
|
520
|
+
`Existing compound child graph is invalid: ${ child.blockName } must declare a parent block.`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
for ( let index = 0; index < child.ancestorBlockNames.length; index += 1 ) {
|
|
527
|
+
const ancestorBlockName = child.ancestorBlockNames[ index ];
|
|
528
|
+
const ancestorChild = childByBlockName.get( ancestorBlockName );
|
|
529
|
+
|
|
530
|
+
if ( ! ancestorChild ) {
|
|
531
|
+
throw new Error(
|
|
532
|
+
`Existing compound child graph is invalid: ${ child.blockName } references missing ancestor ${ ancestorBlockName }.`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if ( ! ancestorChild.container ) {
|
|
537
|
+
throw new Error(
|
|
538
|
+
`Existing compound child graph is invalid: ${ ancestorChild.blockName } is not declared as a container child but has nested descendants.`
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const expectedAncestorRoute = child.ancestorBlockNames.slice( 0, index );
|
|
543
|
+
const ancestorRouteMatches =
|
|
544
|
+
ancestorChild.ancestorBlockNames.length === expectedAncestorRoute.length &&
|
|
545
|
+
expectedAncestorRoute.every(
|
|
546
|
+
( expectedAncestorBlockName, expectedIndex ) =>
|
|
547
|
+
ancestorChild.ancestorBlockNames[ expectedIndex ] ===
|
|
548
|
+
expectedAncestorBlockName
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
if ( ! ancestorRouteMatches ) {
|
|
552
|
+
throw new Error(
|
|
553
|
+
`Existing compound child graph is invalid: ${ ancestorChild.blockName } is not on the declared ancestor route for ${ child.blockName }.`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const nextBlockName =
|
|
558
|
+
child.ancestorBlockNames[ index + 1 ] ?? child.blockName;
|
|
559
|
+
if ( ancestorChild.directAllowedBlocks.includes( nextBlockName ) ) {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
throw new Error(
|
|
564
|
+
`Existing compound child graph is invalid: ${ ancestorChild.blockName } does not currently allow ${ nextBlockName } as a direct child.`
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function resolveExistingCompoundChild(
|
|
571
|
+
value: string,
|
|
572
|
+
existingChildren: ExistingCompoundChild[]
|
|
573
|
+
): ExistingCompoundChild {
|
|
574
|
+
const trimmedValue = value.trim();
|
|
575
|
+
if ( trimmedValue.length === 0 ) {
|
|
576
|
+
throw new Error( 'Ancestor references must not be empty.' );
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const normalizedCandidate = resolveValidatedBlockSlug(
|
|
580
|
+
trimmedValue.includes( '/' )
|
|
581
|
+
? trimmedValue.slice( trimmedValue.lastIndexOf( '/' ) + 1 )
|
|
582
|
+
: trimmedValue
|
|
583
|
+
);
|
|
584
|
+
const resolved = existingChildren.find(
|
|
585
|
+
( child ) =>
|
|
586
|
+
child.blockName === trimmedValue ||
|
|
587
|
+
child.folderSlug === trimmedValue ||
|
|
588
|
+
child.key === normalizedCandidate ||
|
|
589
|
+
child.folderSlug === `${ PARENT_BLOCK_SLUG }-${ normalizedCandidate }`
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
if ( ! resolved ) {
|
|
593
|
+
throw new Error(
|
|
594
|
+
`Unable to resolve compound child ancestor "${ value }". Use an existing child key, folder slug, or block name.`
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return resolved;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function ensureUniqueAncestorChain(
|
|
602
|
+
ancestors: ExistingCompoundChild[]
|
|
603
|
+
): ExistingCompoundChild[] {
|
|
604
|
+
const seenKeys = new Set< string >();
|
|
605
|
+
|
|
606
|
+
return ancestors.filter( ( ancestor ) => {
|
|
607
|
+
if ( seenKeys.has( ancestor.key ) ) {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
seenKeys.add( ancestor.key );
|
|
612
|
+
return true;
|
|
613
|
+
} );
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function validateAncestorChain(
|
|
617
|
+
ancestorChain: ExistingCompoundChild[]
|
|
618
|
+
) {
|
|
619
|
+
for ( let index = 0; index < ancestorChain.length - 1; index += 1 ) {
|
|
620
|
+
const currentAncestor = ancestorChain[ index ];
|
|
621
|
+
const nextAncestor = ancestorChain[ index + 1 ];
|
|
622
|
+
|
|
623
|
+
if ( currentAncestor.directAllowedBlocks.includes( nextAncestor.blockName ) ) {
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
throw new Error(
|
|
628
|
+
`Invalid ancestor chain: ${ currentAncestor.blockName } does not currently allow ${ nextAncestor.blockName } as a direct child.`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function validateNestedAncestorTarget( ancestorChain: ExistingCompoundChild[] ) {
|
|
634
|
+
const directAncestor = ancestorChain[ ancestorChain.length - 1 ];
|
|
635
|
+
|
|
636
|
+
if ( ! directAncestor ) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if ( directAncestor.container ) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
throw new Error(
|
|
645
|
+
`Cannot nest descendants under ${ directAncestor.blockName } because it is not declared as a container child. Re-add that child with --container or target an existing container child.`
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function findCompoundChildSpecSource(
|
|
650
|
+
childrenRegistrySource: string,
|
|
651
|
+
childKey: string
|
|
652
|
+
): string | null {
|
|
653
|
+
const childSpecPattern = /\{[\s\S]*?key:\s*["'][^"']+["'][\s\S]*?\n\s*\},/g;
|
|
654
|
+
|
|
655
|
+
for ( const match of childrenRegistrySource.matchAll( childSpecPattern ) ) {
|
|
656
|
+
const candidate = match[ 0 ];
|
|
657
|
+
if (
|
|
658
|
+
candidate.includes( `key: "${ childKey }"` ) ||
|
|
659
|
+
candidate.includes( `key: '${ childKey }'` )
|
|
660
|
+
) {
|
|
661
|
+
return candidate;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function validateAncestorInstantiability(
|
|
669
|
+
childrenFile: string,
|
|
670
|
+
ancestorChain: ExistingCompoundChild[]
|
|
671
|
+
) {
|
|
672
|
+
if ( ancestorChain.length === 0 ) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const childrenRegistrySource = fs.readFileSync( childrenFile, 'utf8' );
|
|
677
|
+
|
|
678
|
+
for ( const ancestor of ancestorChain ) {
|
|
679
|
+
const childSpecSource = findCompoundChildSpecSource(
|
|
680
|
+
childrenRegistrySource,
|
|
681
|
+
ancestor.key
|
|
682
|
+
);
|
|
683
|
+
if ( ! childSpecSource ) {
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const supportsInserterMatch = childSpecSource.match(
|
|
688
|
+
/supportsInserter:\s*(true|false)/
|
|
689
|
+
);
|
|
690
|
+
const supportsInserter = supportsInserterMatch?.[ 1 ] === 'true';
|
|
691
|
+
const hasTemplateInstances = ! /templateInstances:\s*\[\s*\]/.test( childSpecSource );
|
|
692
|
+
|
|
693
|
+
if ( supportsInserter || hasTemplateInstances ) {
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
throw new Error(
|
|
698
|
+
`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.`
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function formatProjectRelativePath( filePath: string ): string {
|
|
704
|
+
return path.relative( PROJECT_ROOT, filePath ) || '.';
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function buildCompoundChildGraphNodes(
|
|
708
|
+
existingChildren: ExistingCompoundChild[],
|
|
709
|
+
prospectiveChild: CompoundChildGraphNode
|
|
710
|
+
): CompoundChildGraphNode[] {
|
|
711
|
+
return [
|
|
712
|
+
...existingChildren.map( ( child ) => ( {
|
|
713
|
+
blockName: child.blockName,
|
|
714
|
+
container: child.container,
|
|
715
|
+
directParentBlockName: resolveDirectParentBlockName( child ),
|
|
716
|
+
isProspective: false,
|
|
717
|
+
key: child.key,
|
|
718
|
+
placement: child.placement,
|
|
719
|
+
supportsInserter: child.supportsInserter,
|
|
720
|
+
title: child.title,
|
|
721
|
+
} ) ),
|
|
722
|
+
prospectiveChild,
|
|
723
|
+
];
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function renderCompoundChildGraphPreview(
|
|
727
|
+
graphNodes: CompoundChildGraphNode[],
|
|
728
|
+
plannedWritePaths: string[],
|
|
729
|
+
options: {
|
|
730
|
+
dryRun: boolean;
|
|
731
|
+
}
|
|
732
|
+
): string {
|
|
733
|
+
const childrenByParent = new Map< string, CompoundChildGraphNode[] >();
|
|
734
|
+
|
|
735
|
+
for ( const graphNode of graphNodes ) {
|
|
736
|
+
const existingChildren = childrenByParent.get( graphNode.directParentBlockName ) ?? [];
|
|
737
|
+
existingChildren.push( graphNode );
|
|
738
|
+
childrenByParent.set( graphNode.directParentBlockName, existingChildren );
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const renderChildLines = (
|
|
742
|
+
parentBlockName: string,
|
|
743
|
+
indent: string
|
|
744
|
+
): string[] => {
|
|
745
|
+
const directChildren = [ ...( childrenByParent.get( parentBlockName ) ?? [] ) ].sort(
|
|
746
|
+
( left, right ) => left.key.localeCompare( right.key )
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
return directChildren.flatMap( ( child ) => {
|
|
750
|
+
const labels = [
|
|
751
|
+
child.isProspective ? 'new' : 'existing',
|
|
752
|
+
child.placement === 'root' ? 'root' : 'nested',
|
|
753
|
+
child.container ? 'container' : 'leaf',
|
|
754
|
+
child.supportsInserter ? 'visible' : 'hidden',
|
|
755
|
+
];
|
|
756
|
+
const line = `${ indent }- ${ child.key } -> ${ child.blockName } [${ labels.join( ', ' ) }]`;
|
|
757
|
+
return [ line, ...renderChildLines( child.blockName, `${ indent } ` ) ];
|
|
758
|
+
} );
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
return [
|
|
762
|
+
'Compound child graph preview',
|
|
763
|
+
`Parent block: ${ PARENT_BLOCK_NAME }`,
|
|
764
|
+
...renderChildLines( PARENT_BLOCK_NAME, '' ),
|
|
765
|
+
'',
|
|
766
|
+
'Planned writes',
|
|
767
|
+
...plannedWritePaths.map( ( plannedWritePath ) => `- ${ plannedWritePath }` ),
|
|
768
|
+
options.dryRun ? '' : '',
|
|
769
|
+
options.dryRun
|
|
770
|
+
? 'Dry run only; no files were written.'
|
|
771
|
+
: 'Applying these updates now.',
|
|
772
|
+
]
|
|
773
|
+
.filter( ( line, index, lines ) => {
|
|
774
|
+
if ( line.length > 0 ) {
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return index > 0 && lines[ index - 1 ].length > 0;
|
|
779
|
+
} )
|
|
780
|
+
.join( '\n' );
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function updateAllowedBlocks(
|
|
784
|
+
filePath: string,
|
|
785
|
+
blockName: string
|
|
786
|
+
) {
|
|
787
|
+
const blockJson = readBlockJsonDocument( filePath );
|
|
788
|
+
const existingAllowedBlocks = Array.isArray( blockJson.allowedBlocks )
|
|
789
|
+
? blockJson.allowedBlocks.filter(
|
|
790
|
+
( value ): value is string => typeof value === 'string'
|
|
791
|
+
)
|
|
792
|
+
: [];
|
|
793
|
+
|
|
794
|
+
if ( existingAllowedBlocks.includes( blockName ) ) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
blockJson.allowedBlocks = [ ...existingAllowedBlocks, blockName ];
|
|
799
|
+
writeBlockJsonDocument( filePath, blockJson );
|
|
800
|
+
}
|
|
801
|
+
|
|
303
802
|
function renderBlockJson(
|
|
304
803
|
childBlockName: string,
|
|
305
804
|
childFolderSlug: string,
|
|
306
|
-
childTitle: string
|
|
805
|
+
childTitle: string,
|
|
806
|
+
options: {
|
|
807
|
+
allowedBlocks?: string[];
|
|
808
|
+
ancestorBlockNames: string[];
|
|
809
|
+
container: boolean;
|
|
810
|
+
supportsInserter: boolean;
|
|
811
|
+
}
|
|
307
812
|
): string {
|
|
308
813
|
const childCssClassName = buildBlockCssClassName( PARENT_BLOCK_NAMESPACE, childFolderSlug );
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
814
|
+
const document: Record< string, unknown > = {
|
|
815
|
+
$schema: 'https://schemas.wp.org/trunk/block.json',
|
|
816
|
+
apiVersion: 3,
|
|
817
|
+
name: childBlockName,
|
|
818
|
+
version: '{{blockMetadataVersion}}',
|
|
819
|
+
title: childTitle,
|
|
820
|
+
category: '{{compoundChildCategory}}',
|
|
821
|
+
icon: '{{compoundChildIcon}}',
|
|
822
|
+
description: `Internal item block used by ${ PARENT_BLOCK_TITLE }.`,
|
|
823
|
+
example: {},
|
|
824
|
+
supports: {
|
|
825
|
+
html: false,
|
|
826
|
+
inserter: options.supportsInserter,
|
|
827
|
+
reusable: false,
|
|
828
|
+
},
|
|
829
|
+
attributes: {
|
|
830
|
+
title: {
|
|
831
|
+
type: 'string',
|
|
832
|
+
source: 'html',
|
|
833
|
+
selector: `.${ childCssClassName }__title`,
|
|
834
|
+
default: childTitle,
|
|
326
835
|
},
|
|
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
|
-
},
|
|
836
|
+
body: {
|
|
837
|
+
type: 'string',
|
|
838
|
+
source: 'html',
|
|
839
|
+
selector: `.${ childCssClassName }__body`,
|
|
840
|
+
default: CHILD_PLACEHOLDER,
|
|
340
841
|
},
|
|
341
|
-
textdomain: TEXT_DOMAIN,
|
|
342
|
-
editorScript: 'file:./index.js',
|
|
343
842
|
},
|
|
344
|
-
|
|
345
|
-
'
|
|
346
|
-
|
|
843
|
+
textdomain: TEXT_DOMAIN,
|
|
844
|
+
editorScript: 'file:./index.js',
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
if ( options.ancestorBlockNames.length > 0 ) {
|
|
848
|
+
document.ancestor = options.ancestorBlockNames;
|
|
849
|
+
} else {
|
|
850
|
+
document.parent = [ PARENT_BLOCK_NAME ];
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
if ( options.container || ( options.allowedBlocks && options.allowedBlocks.length > 0 ) ) {
|
|
854
|
+
document.allowedBlocks = options.allowedBlocks ?? [];
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return `${ JSON.stringify( document, null, '\t' ) }\n`;
|
|
347
858
|
}
|
|
348
859
|
|
|
349
860
|
function renderTypesFile(
|
|
@@ -491,10 +1002,15 @@ function renderEditFile(
|
|
|
491
1002
|
const childCssClassName = buildBlockCssClassName( PARENT_BLOCK_NAMESPACE, childFolderSlug );
|
|
492
1003
|
|
|
493
1004
|
return `import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';
|
|
494
|
-
import { RichText, useBlockProps } from '@wordpress/block-editor';
|
|
1005
|
+
import { InnerBlocks, RichText, useBlockProps } from '@wordpress/block-editor';
|
|
495
1006
|
import { Notice } from '@wordpress/components';
|
|
496
1007
|
import { __ } from '@wordpress/i18n';
|
|
497
1008
|
|
|
1009
|
+
import metadata from './block-metadata';
|
|
1010
|
+
import {
|
|
1011
|
+
\tgetChildInnerBlocksPropsOptions,
|
|
1012
|
+
\thasNestedChildBlocks,
|
|
1013
|
+
} from '../${ PARENT_BLOCK_SLUG }/children';
|
|
498
1014
|
import { useTypiaValidation } from './hooks';
|
|
499
1015
|
import type { ${ childTypeName } } from './types';
|
|
500
1016
|
import {
|
|
@@ -503,6 +1019,14 @@ import {
|
|
|
503
1019
|
} from './validators';
|
|
504
1020
|
|
|
505
1021
|
type EditProps = BlockEditProps< ${ childTypeName } >;
|
|
1022
|
+
type CompoundInnerBlocksProps = Parameters< typeof InnerBlocks >[ 0 ] & {
|
|
1023
|
+
\tdefaultBlock?: [ string, Record< string, unknown > ];
|
|
1024
|
+
\tdirectInsert?: boolean;
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
const TypedInnerBlocks = InnerBlocks as unknown as (
|
|
1028
|
+
\tprops: CompoundInnerBlocksProps
|
|
1029
|
+
) => ReturnType< typeof InnerBlocks >;
|
|
506
1030
|
|
|
507
1031
|
export default function Edit( {
|
|
508
1032
|
\tattributes,
|
|
@@ -513,6 +1037,10 @@ export default function Edit( {
|
|
|
513
1037
|
\t\tattributes,
|
|
514
1038
|
\t\tvalidate${ childInterfaceName }
|
|
515
1039
|
\t);
|
|
1040
|
+
\tconst nestedInnerBlocksPropsOptions = getChildInnerBlocksPropsOptions(
|
|
1041
|
+
\t\tmetadata.name
|
|
1042
|
+
\t);
|
|
1043
|
+
\tconst showsNestedChildren = hasNestedChildBlocks( metadata.name );
|
|
516
1044
|
|
|
517
1045
|
\treturn (
|
|
518
1046
|
\t\t<div { ...useBlockProps( { className: '${ childCssClassName }' } ) }>
|
|
@@ -539,6 +1067,13 @@ export default function Edit( {
|
|
|
539
1067
|
\t\t\t\t\t</ul>
|
|
540
1068
|
\t\t\t\t</Notice>
|
|
541
1069
|
\t\t\t) }
|
|
1070
|
+
\t\t\t{ showsNestedChildren && (
|
|
1071
|
+
\t\t\t\t<div className="${ childCssClassName }__children">
|
|
1072
|
+
\t\t\t\t\t<TypedInnerBlocks
|
|
1073
|
+
\t\t\t\t\t\t{ ...( nestedInnerBlocksPropsOptions ?? {} ) }
|
|
1074
|
+
\t\t\t\t\t/>
|
|
1075
|
+
\t\t\t\t</div>
|
|
1076
|
+
\t\t\t) }
|
|
542
1077
|
\t\t</div>
|
|
543
1078
|
\t);
|
|
544
1079
|
}
|
|
@@ -548,8 +1083,10 @@ export default function Edit( {
|
|
|
548
1083
|
function renderSaveFile( childFolderSlug: string, childTypeName: string ): string {
|
|
549
1084
|
const childCssClassName = buildBlockCssClassName( PARENT_BLOCK_NAMESPACE, childFolderSlug );
|
|
550
1085
|
|
|
551
|
-
return `import { RichText, useBlockProps } from '@wordpress/block-editor';
|
|
1086
|
+
return `import { InnerBlocks, RichText, useBlockProps } from '@wordpress/block-editor';
|
|
552
1087
|
|
|
1088
|
+
import metadata from './block-metadata';
|
|
1089
|
+
import { hasNestedChildBlocks } from '../${ PARENT_BLOCK_SLUG }/children';
|
|
553
1090
|
import type { ${ childTypeName } } from './types';
|
|
554
1091
|
|
|
555
1092
|
export default function Save( {
|
|
@@ -557,6 +1094,8 @@ export default function Save( {
|
|
|
557
1094
|
}: {
|
|
558
1095
|
\tattributes: ${ childTypeName };
|
|
559
1096
|
} ) {
|
|
1097
|
+
\tconst showsNestedChildren = hasNestedChildBlocks( metadata.name );
|
|
1098
|
+
|
|
560
1099
|
\treturn (
|
|
561
1100
|
\t\t<div { ...useBlockProps.save( { className: '${ childCssClassName }' } ) }>
|
|
562
1101
|
\t\t\t<RichText.Content
|
|
@@ -569,13 +1108,18 @@ export default function Save( {
|
|
|
569
1108
|
\t\t\t\tclassName="${ childCssClassName }__body"
|
|
570
1109
|
\t\t\t\tvalue={ attributes.body }
|
|
571
1110
|
\t\t\t/>
|
|
1111
|
+
\t\t\t{ showsNestedChildren && (
|
|
1112
|
+
\t\t\t\t<div className="${ childCssClassName }__children">
|
|
1113
|
+
\t\t\t\t\t<InnerBlocks.Content />
|
|
1114
|
+
\t\t\t\t</div>
|
|
1115
|
+
\t\t\t) }
|
|
572
1116
|
\t\t</div>
|
|
573
1117
|
\t);
|
|
574
1118
|
}
|
|
575
1119
|
`;
|
|
576
1120
|
}
|
|
577
1121
|
|
|
578
|
-
function renderIndexFile( childTypeName: string
|
|
1122
|
+
function renderIndexFile( childTypeName: string ): string {
|
|
579
1123
|
return `import {
|
|
580
1124
|
\tregisterScaffoldBlockType,
|
|
581
1125
|
\ttype BlockConfiguration,
|
|
@@ -604,14 +1148,70 @@ registerScaffoldBlockType( registration.name, registration.settings );
|
|
|
604
1148
|
`;
|
|
605
1149
|
}
|
|
606
1150
|
|
|
1151
|
+
function renderChildSpecLines(
|
|
1152
|
+
options: {
|
|
1153
|
+
ancestorKeys: string[];
|
|
1154
|
+
blockName: string;
|
|
1155
|
+
container: boolean;
|
|
1156
|
+
folderSlug: string;
|
|
1157
|
+
key: string;
|
|
1158
|
+
placement: 'nested' | 'root';
|
|
1159
|
+
seedTemplate: boolean;
|
|
1160
|
+
supportsInserter: boolean;
|
|
1161
|
+
title: string;
|
|
1162
|
+
}
|
|
1163
|
+
): string[] {
|
|
1164
|
+
const templateLines =
|
|
1165
|
+
options.seedTemplate
|
|
1166
|
+
? [
|
|
1167
|
+
'\ttemplateInstances: [',
|
|
1168
|
+
'\t\t{',
|
|
1169
|
+
`\t\t\tbody: ${ JSON.stringify( CHILD_PLACEHOLDER ) },`,
|
|
1170
|
+
`\t\t\ttitle: ${ JSON.stringify( options.title ) },`,
|
|
1171
|
+
'\t\t},',
|
|
1172
|
+
'\t],',
|
|
1173
|
+
]
|
|
1174
|
+
: [ '\ttemplateInstances: [],' ];
|
|
1175
|
+
|
|
1176
|
+
return [
|
|
1177
|
+
'{',
|
|
1178
|
+
`\tancestorKeys: [ ${ options.ancestorKeys.map( ( value ) => JSON.stringify( value ) ).join( ', ' ) } ],`,
|
|
1179
|
+
`\tblockName: ${ JSON.stringify( options.blockName ) },`,
|
|
1180
|
+
`\tbodyPlaceholder: ${ JSON.stringify( CHILD_PLACEHOLDER ) },`,
|
|
1181
|
+
`\tcontainer: ${ options.container ? 'true' : 'false' },`,
|
|
1182
|
+
`\tfolderSlug: ${ JSON.stringify( options.folderSlug ) },`,
|
|
1183
|
+
`\tkey: ${ JSON.stringify( options.key ) },`,
|
|
1184
|
+
`\tplacement: ${ JSON.stringify( options.placement ) },`,
|
|
1185
|
+
`\tsupportsInserter: ${ options.supportsInserter ? 'true' : 'false' },`,
|
|
1186
|
+
...templateLines,
|
|
1187
|
+
`\ttitle: ${ JSON.stringify( options.title ) },`,
|
|
1188
|
+
'},',
|
|
1189
|
+
];
|
|
1190
|
+
}
|
|
1191
|
+
|
|
607
1192
|
function main() {
|
|
608
|
-
const {
|
|
1193
|
+
const {
|
|
1194
|
+
ancestorValues,
|
|
1195
|
+
container,
|
|
1196
|
+
dryRun,
|
|
1197
|
+
inserter,
|
|
1198
|
+
slug,
|
|
1199
|
+
title,
|
|
1200
|
+
} = parseArgs();
|
|
609
1201
|
const normalizedSlug = slug ? resolveValidatedBlockSlug( slug ) : '';
|
|
610
1202
|
|
|
611
1203
|
if ( normalizedSlug.length === 0 ) {
|
|
612
1204
|
throw new Error( 'Use a child slug with lowercase letters, numbers, and hyphens only.' );
|
|
613
1205
|
}
|
|
614
1206
|
|
|
1207
|
+
const existingChildren = listExistingCompoundChildren();
|
|
1208
|
+
validateExistingCompoundChildGraph( existingChildren );
|
|
1209
|
+
const ancestorChain = ensureUniqueAncestorChain(
|
|
1210
|
+
ancestorValues.map( ( value ) =>
|
|
1211
|
+
resolveExistingCompoundChild( value, existingChildren )
|
|
1212
|
+
)
|
|
1213
|
+
);
|
|
1214
|
+
validateAncestorChain( ancestorChain );
|
|
615
1215
|
const childTitle = title?.trim().length ? title.trim() : toTitleCase( normalizedSlug );
|
|
616
1216
|
const childFolderSlug = `${ PARENT_BLOCK_SLUG }-${ normalizedSlug }`;
|
|
617
1217
|
const childBlockName = `${ PARENT_BLOCK_NAME }-${ normalizedSlug }`;
|
|
@@ -628,6 +1228,10 @@ function main() {
|
|
|
628
1228
|
);
|
|
629
1229
|
const blockConfigFile = path.join( PROJECT_ROOT, 'scripts', 'block-config.ts' );
|
|
630
1230
|
|
|
1231
|
+
if ( ancestorChain.some( ( ancestor ) => ancestor.key === normalizedSlug ) ) {
|
|
1232
|
+
throw new Error( 'A child block cannot list itself as an ancestor.' );
|
|
1233
|
+
}
|
|
1234
|
+
|
|
631
1235
|
if ( fs.existsSync( childDir ) ) {
|
|
632
1236
|
throw new Error( `Child block already exists: ${ childFolderSlug }` );
|
|
633
1237
|
}
|
|
@@ -638,11 +1242,69 @@ function main() {
|
|
|
638
1242
|
);
|
|
639
1243
|
}
|
|
640
1244
|
|
|
1245
|
+
validateAncestorInstantiability( childrenFile, ancestorChain );
|
|
1246
|
+
validateNestedAncestorTarget( ancestorChain );
|
|
1247
|
+
const supportsInserter =
|
|
1248
|
+
inserter === 'visible'
|
|
1249
|
+
? true
|
|
1250
|
+
: inserter === 'hidden'
|
|
1251
|
+
? false
|
|
1252
|
+
: container || ancestorChain.length > 0;
|
|
1253
|
+
const placement = ancestorChain.length > 0 ? 'nested' : 'root';
|
|
1254
|
+
const seedTemplate = supportsInserter || container || ancestorChain.length > 0;
|
|
1255
|
+
const directAllowedBlocks: string[] = [];
|
|
1256
|
+
const directAncestor = ancestorChain[ ancestorChain.length - 1 ];
|
|
1257
|
+
const plannedWritePaths = [
|
|
1258
|
+
formatProjectRelativePath( path.join( childDir, 'block.json' ) ),
|
|
1259
|
+
formatProjectRelativePath( path.join( childDir, 'types.ts' ) ),
|
|
1260
|
+
formatProjectRelativePath( path.join( childDir, 'typia.manifest.json' ) ),
|
|
1261
|
+
formatProjectRelativePath( path.join( childDir, 'block-metadata.ts' ) ),
|
|
1262
|
+
formatProjectRelativePath( path.join( childDir, 'manifest-document.ts' ) ),
|
|
1263
|
+
formatProjectRelativePath( path.join( childDir, 'manifest-defaults-document.ts' ) ),
|
|
1264
|
+
formatProjectRelativePath( path.join( childDir, 'hooks.ts' ) ),
|
|
1265
|
+
formatProjectRelativePath( path.join( childDir, 'validators.ts' ) ),
|
|
1266
|
+
formatProjectRelativePath( path.join( childDir, 'edit.tsx' ) ),
|
|
1267
|
+
formatProjectRelativePath( path.join( childDir, 'save.tsx' ) ),
|
|
1268
|
+
formatProjectRelativePath( path.join( childDir, 'index.tsx' ) ),
|
|
1269
|
+
formatProjectRelativePath( childrenFile ),
|
|
1270
|
+
formatProjectRelativePath( blockConfigFile ),
|
|
1271
|
+
formatProjectRelativePath(
|
|
1272
|
+
placement === 'root' ? PARENT_BLOCK_JSON_PATH : directAncestor.blockJsonPath
|
|
1273
|
+
),
|
|
1274
|
+
];
|
|
1275
|
+
|
|
1276
|
+
console.log(
|
|
1277
|
+
renderCompoundChildGraphPreview(
|
|
1278
|
+
buildCompoundChildGraphNodes( existingChildren, {
|
|
1279
|
+
blockName: childBlockName,
|
|
1280
|
+
container,
|
|
1281
|
+
directParentBlockName:
|
|
1282
|
+
directAncestor?.blockName ?? PARENT_BLOCK_NAME,
|
|
1283
|
+
isProspective: true,
|
|
1284
|
+
key: normalizedSlug,
|
|
1285
|
+
placement,
|
|
1286
|
+
supportsInserter,
|
|
1287
|
+
title: childTitle,
|
|
1288
|
+
} ),
|
|
1289
|
+
plannedWritePaths,
|
|
1290
|
+
{ dryRun }
|
|
1291
|
+
)
|
|
1292
|
+
);
|
|
1293
|
+
|
|
1294
|
+
if ( dryRun ) {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
641
1298
|
fs.mkdirSync( childDir, { recursive: true } );
|
|
642
1299
|
|
|
643
1300
|
fs.writeFileSync(
|
|
644
1301
|
path.join( childDir, 'block.json' ),
|
|
645
|
-
renderBlockJson( childBlockName, childFolderSlug, childTitle
|
|
1302
|
+
renderBlockJson( childBlockName, childFolderSlug, childTitle, {
|
|
1303
|
+
allowedBlocks: directAllowedBlocks,
|
|
1304
|
+
ancestorBlockNames: ancestorChain.map( ( ancestor ) => ancestor.blockName ),
|
|
1305
|
+
container,
|
|
1306
|
+
supportsInserter,
|
|
1307
|
+
} ),
|
|
646
1308
|
'utf8'
|
|
647
1309
|
);
|
|
648
1310
|
fs.writeFileSync(
|
|
@@ -688,14 +1350,24 @@ function main() {
|
|
|
688
1350
|
);
|
|
689
1351
|
fs.writeFileSync(
|
|
690
1352
|
path.join( childDir, 'index.tsx' ),
|
|
691
|
-
renderIndexFile( childTypeName
|
|
1353
|
+
renderIndexFile( childTypeName ),
|
|
692
1354
|
'utf8'
|
|
693
1355
|
);
|
|
694
1356
|
|
|
695
1357
|
insertBeforeMarker(
|
|
696
1358
|
childrenFile,
|
|
697
|
-
|
|
698
|
-
|
|
1359
|
+
CHILD_SPEC_MARKER,
|
|
1360
|
+
renderChildSpecLines( {
|
|
1361
|
+
ancestorKeys: ancestorChain.map( ( ancestor ) => ancestor.key ),
|
|
1362
|
+
blockName: childBlockName,
|
|
1363
|
+
container,
|
|
1364
|
+
folderSlug: childFolderSlug,
|
|
1365
|
+
key: normalizedSlug,
|
|
1366
|
+
placement,
|
|
1367
|
+
seedTemplate,
|
|
1368
|
+
supportsInserter,
|
|
1369
|
+
title: childTitle,
|
|
1370
|
+
} )
|
|
699
1371
|
);
|
|
700
1372
|
insertBeforeMarker(
|
|
701
1373
|
blockConfigFile,
|
|
@@ -709,6 +1381,13 @@ function main() {
|
|
|
709
1381
|
]
|
|
710
1382
|
);
|
|
711
1383
|
|
|
1384
|
+
if ( placement === 'root' ) {
|
|
1385
|
+
updateAllowedBlocks( PARENT_BLOCK_JSON_PATH, childBlockName );
|
|
1386
|
+
} else {
|
|
1387
|
+
const directAncestor = ancestorChain[ ancestorChain.length - 1 ];
|
|
1388
|
+
updateAllowedBlocks( directAncestor.blockJsonPath, childBlockName );
|
|
1389
|
+
}
|
|
1390
|
+
|
|
712
1391
|
console.log( `✅ Added compound child block ${ childBlockName }` );
|
|
713
1392
|
console.log(
|
|
714
1393
|
'Run `sync-types` next to generate block.json metadata, manifests, schemas, and PHP validators for the new child block.'
|