@wp-typia/project-tools 0.18.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 +1 -1
- package/dist/runtime/block-generator-service-core.js +11 -7
- package/dist/runtime/block-generator-service-spec.d.ts +8 -1
- package/dist/runtime/block-generator-service-spec.js +43 -1
- package/dist/runtime/built-in-block-code-templates/compound-child.d.ts +1 -1
- package/dist/runtime/built-in-block-code-templates/compound-child.js +14 -9
- 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 +100 -43
- 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 +4 -1
- package/dist/runtime/cli-add-block.js +55 -26
- package/dist/runtime/cli-add-shared.d.ts +3 -1
- package/dist/runtime/cli-add-shared.js +12 -12
- package/dist/runtime/cli-core.d.ts +2 -0
- package/dist/runtime/cli-core.js +1 -0
- package/dist/runtime/cli-help.js +4 -3
- package/dist/runtime/cli-scaffold.d.ts +3 -1
- package/dist/runtime/cli-scaffold.js +88 -12
- package/dist/runtime/cli-templates.js +26 -1
- 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 +2 -0
- package/dist/runtime/index.js +1 -0
- 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/scaffold-answer-resolution.js +10 -6
- package/dist/runtime/scaffold-documents.js +22 -2
- package/dist/runtime/scaffold-identifiers.d.ts +17 -0
- package/dist/runtime/scaffold-identifiers.js +22 -0
- package/dist/runtime/scaffold-onboarding.js +21 -13
- package/dist/runtime/scaffold-template-variables.js +22 -0
- package/dist/runtime/scaffold.d.ts +16 -1
- package/dist/runtime/scaffold.js +7 -4
- package/dist/runtime/template-source.js +5 -3
- package/dist/runtime/workspace-project.js +1 -1
- package/package.json +7 -2
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +318 -18
|
@@ -8,6 +8,7 @@ import type { PackageManagerId } from "./package-managers.js";
|
|
|
8
8
|
*/
|
|
9
9
|
export interface ScaffoldAnswers {
|
|
10
10
|
author: string;
|
|
11
|
+
compoundInnerBlocksPreset?: import("./compound-inner-blocks.js").CompoundInnerBlocksPresetId;
|
|
11
12
|
dataStorageMode?: DataStorageMode;
|
|
12
13
|
description: string;
|
|
13
14
|
/** Block namespace used in generated block names such as `namespace/slug`. */
|
|
@@ -29,6 +30,8 @@ export type PersistencePolicy = (typeof PERSISTENCE_POLICIES)[number];
|
|
|
29
30
|
* Normalized template variables shared by built-in and remote scaffold flows.
|
|
30
31
|
*/
|
|
31
32
|
export interface ScaffoldTemplateVariables extends Record<string, string> {
|
|
33
|
+
alternateRenderTargetsCsv: string;
|
|
34
|
+
alternateRenderTargetsJson: string;
|
|
32
35
|
apiClientPackageVersion: string;
|
|
33
36
|
author: string;
|
|
34
37
|
blockRuntimePackageVersion: string;
|
|
@@ -42,6 +45,17 @@ export interface ScaffoldTemplateVariables extends Record<string, string> {
|
|
|
42
45
|
compoundChildIcon: string;
|
|
43
46
|
compoundChildTitleJson: string;
|
|
44
47
|
compoundPersistenceEnabled: "false" | "true";
|
|
48
|
+
compoundInnerBlocksDirectInsert: "false" | "true";
|
|
49
|
+
compoundInnerBlocksOrientation: "" | "horizontal" | "vertical";
|
|
50
|
+
compoundInnerBlocksOrientationExpression: string;
|
|
51
|
+
compoundInnerBlocksPreset: string;
|
|
52
|
+
compoundInnerBlocksPresetDescription: string;
|
|
53
|
+
compoundInnerBlocksPresetLabel: string;
|
|
54
|
+
compoundInnerBlocksTemplateLockExpression: string;
|
|
55
|
+
hasAlternateEmailRenderTarget: "false" | "true";
|
|
56
|
+
hasAlternateMjmlRenderTarget: "false" | "true";
|
|
57
|
+
hasAlternatePlainTextRenderTarget: "false" | "true";
|
|
58
|
+
hasAlternateRenderTargets: "false" | "true";
|
|
45
59
|
projectToolsPackageVersion: string;
|
|
46
60
|
cssClassName: string;
|
|
47
61
|
dashCase: string;
|
|
@@ -131,6 +145,7 @@ export interface ScaffoldProgressEvent {
|
|
|
131
145
|
}
|
|
132
146
|
interface ScaffoldProjectOptions {
|
|
133
147
|
allowExistingDir?: boolean;
|
|
148
|
+
alternateRenderTargets?: string;
|
|
134
149
|
answers: ScaffoldAnswers;
|
|
135
150
|
cwd?: string;
|
|
136
151
|
dataStorageMode?: DataStorageMode;
|
|
@@ -163,4 +178,4 @@ export { collectScaffoldAnswers, detectAuthor, getDefaultAnswers, resolvePackage
|
|
|
163
178
|
export { getTemplateVariables } from "./scaffold-template-variables.js";
|
|
164
179
|
export declare function isDataStorageMode(value: string): value is DataStorageMode;
|
|
165
180
|
export declare function isPersistencePolicy(value: string): value is PersistencePolicy;
|
|
166
|
-
export declare function scaffoldProject({ projectDir, templateId, answers, dataStorageMode, persistencePolicy, packageManager, externalLayerId, externalLayerSource, externalLayerSourceLabel, repositoryReference, cwd, allowExistingDir, noInstall, installDependencies, onProgress, variant, withMigrationUi, withTestPreset, withWpEnv, }: ScaffoldProjectOptions): Promise<ScaffoldProjectResult>;
|
|
181
|
+
export declare function scaffoldProject({ projectDir, templateId, answers, alternateRenderTargets, dataStorageMode, persistencePolicy, packageManager, externalLayerId, externalLayerSource, externalLayerSourceLabel, repositoryReference, cwd, allowExistingDir, noInstall, installDependencies, onProgress, variant, withMigrationUi, withTestPreset, withWpEnv, }: ScaffoldProjectOptions): Promise<ScaffoldProjectResult>;
|
package/dist/runtime/scaffold.js
CHANGED
|
@@ -12,6 +12,7 @@ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./tem
|
|
|
12
12
|
import { resolveTemplateSource } from "./template-source.js";
|
|
13
13
|
import { BlockGeneratorService, } from "./block-generator-service.js";
|
|
14
14
|
import { getTemplateVariables } from "./scaffold-template-variables.js";
|
|
15
|
+
import { assertExternalLayerCompositionOptions, } from "./cli-validation.js";
|
|
15
16
|
const WORKSPACE_TEMPLATE_ALIAS = "workspace";
|
|
16
17
|
export const DATA_STORAGE_MODES = ["post-meta", "custom-table"];
|
|
17
18
|
export const PERSISTENCE_POLICIES = ["authenticated", "public"];
|
|
@@ -32,13 +33,14 @@ function normalizeTemplateSelection(templateId) {
|
|
|
32
33
|
async function reportScaffoldProgress(onProgress, event) {
|
|
33
34
|
await onProgress?.(event);
|
|
34
35
|
}
|
|
35
|
-
export async function scaffoldProject({ projectDir, templateId, answers, dataStorageMode, persistencePolicy, packageManager, externalLayerId, externalLayerSource, externalLayerSourceLabel, repositoryReference, cwd = process.cwd(), allowExistingDir = false, noInstall = false, installDependencies = undefined, onProgress = undefined, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
|
|
36
|
+
export async function scaffoldProject({ projectDir, templateId, answers, alternateRenderTargets, dataStorageMode, persistencePolicy, packageManager, externalLayerId, externalLayerSource, externalLayerSourceLabel, repositoryReference, cwd = process.cwd(), allowExistingDir = false, noInstall = false, installDependencies = undefined, onProgress = undefined, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
|
|
36
37
|
const resolvedTemplateId = normalizeTemplateSelection(templateId);
|
|
37
38
|
const resolvedPackageManager = getPackageManager(packageManager).id;
|
|
38
39
|
const isBuiltInTemplate = isBuiltInTemplateId(resolvedTemplateId);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
assertExternalLayerCompositionOptions({
|
|
41
|
+
externalLayerId,
|
|
42
|
+
externalLayerSource,
|
|
43
|
+
});
|
|
42
44
|
if (isBuiltInTemplate) {
|
|
43
45
|
const blockGeneratorService = new BlockGeneratorService();
|
|
44
46
|
await reportScaffoldProgress(onProgress, {
|
|
@@ -48,6 +50,7 @@ export async function scaffoldProject({ projectDir, templateId, answers, dataSto
|
|
|
48
50
|
});
|
|
49
51
|
const plan = await blockGeneratorService.plan({
|
|
50
52
|
allowExistingDir,
|
|
53
|
+
alternateRenderTargets,
|
|
51
54
|
answers,
|
|
52
55
|
cwd,
|
|
53
56
|
dataStorageMode: dataStorageMode ?? answers.dataStorageMode,
|
|
@@ -3,13 +3,15 @@ import { resolveBuiltInTemplateSource } from './template-builtins.js';
|
|
|
3
3
|
import { parseTemplateLocator, } from './template-source-locators.js';
|
|
4
4
|
import { detectTemplateSourceFormat, getTemplateProjectType, getDefaultCategory, getTemplateVariableContext, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, renderCreateBlockExternalTemplate, } from './template-source-normalization.js';
|
|
5
5
|
import { isOfficialWorkspaceTemplateSeed, resolveTemplateSeed, } from './template-source-seeds.js';
|
|
6
|
+
import { assertBuiltInTemplateVariantAllowed, } from './cli-validation.js';
|
|
6
7
|
export { parseGitHubTemplateLocator, parseNpmTemplateLocator, parseTemplateLocator, } from './template-source-locators.js';
|
|
7
8
|
export { resolveTemplateSeed } from './template-source-seeds.js';
|
|
8
9
|
export async function resolveTemplateSource(templateId, cwd, variables, variant) {
|
|
9
10
|
if (isBuiltInTemplateId(templateId)) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
assertBuiltInTemplateVariantAllowed({
|
|
12
|
+
templateId,
|
|
13
|
+
variant,
|
|
14
|
+
});
|
|
13
15
|
return resolveBuiltInTemplateSource(templateId, {
|
|
14
16
|
persistenceEnabled: variables.compoundPersistenceEnabled === 'true',
|
|
15
17
|
persistencePolicy: variables.persistencePolicy === 'public' ? 'public' : 'authenticated',
|
|
@@ -148,5 +148,5 @@ export function resolveWorkspaceProject(startDir) {
|
|
|
148
148
|
if (workspace) {
|
|
149
149
|
return workspace;
|
|
150
150
|
}
|
|
151
|
-
throw new Error(`This command must run inside
|
|
151
|
+
throw new Error(`This command must run inside an official wp-typia workspace. Create one with \`wp-typia create my-plugin --template workspace\` first (the short alias for \`${WORKSPACE_TEMPLATE_PACKAGE}\`).`);
|
|
152
152
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wp-typia/project-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Project orchestration and programmatic tooling for wp-typia",
|
|
5
5
|
"packageManager": "bun@1.3.11",
|
|
6
6
|
"type": "module",
|
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
"import": "./dist/runtime/cli-scaffold.js",
|
|
38
38
|
"default": "./dist/runtime/cli-scaffold.js"
|
|
39
39
|
},
|
|
40
|
+
"./compound-inner-blocks": {
|
|
41
|
+
"types": "./dist/runtime/compound-inner-blocks.d.ts",
|
|
42
|
+
"import": "./dist/runtime/compound-inner-blocks.js",
|
|
43
|
+
"default": "./dist/runtime/compound-inner-blocks.js"
|
|
44
|
+
},
|
|
40
45
|
"./cli-templates": {
|
|
41
46
|
"types": "./dist/runtime/cli-templates.d.ts",
|
|
42
47
|
"import": "./dist/runtime/cli-templates.js",
|
|
@@ -86,7 +91,7 @@
|
|
|
86
91
|
"test:migration-planning": "bun run build && bun test tests/migration-init.test.ts tests/migration-config.test.ts tests/migration-plan-wizard.test.ts",
|
|
87
92
|
"test:migration-execution": "bun run build && bun test tests/migration-scaffold-diff.test.ts tests/migration-doctor.test.ts tests/migration-fixtures-fuzz.test.ts",
|
|
88
93
|
"test:project-tools": "bun run test:scaffold-core && bun run test:workspace && bun run test:compound && bun run test:migration-planning && bun run test:migration-execution",
|
|
89
|
-
"test:coverage": "bun run build && bun test tests/*.test.ts --coverage --coverage-reporter=lcov --coverage-dir=coverage",
|
|
94
|
+
"test:coverage": "bun run build && bun run --filter wp-typia build && bun test tests/*.test.ts --coverage --coverage-reporter=lcov --coverage-dir=coverage",
|
|
90
95
|
"clean": "rm -rf dist",
|
|
91
96
|
"prepublishOnly": "bun run build"
|
|
92
97
|
},
|
|
@@ -28,25 +28,45 @@ type CompoundParentConfig = {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
type ExistingCompoundChild = {
|
|
31
|
+
ancestorBlockNames: string[];
|
|
31
32
|
blockJsonPath: string;
|
|
32
33
|
blockName: string;
|
|
34
|
+
container: boolean;
|
|
35
|
+
directAllowedBlocks: string[];
|
|
33
36
|
folderSlug: string;
|
|
34
37
|
key: string;
|
|
38
|
+
parentBlockNames: string[];
|
|
39
|
+
placement: 'nested' | 'root';
|
|
40
|
+
supportsInserter: boolean;
|
|
41
|
+
title: string;
|
|
35
42
|
};
|
|
36
43
|
|
|
37
44
|
type ParsedArgs = {
|
|
38
45
|
ancestorValues: string[];
|
|
39
46
|
container: boolean;
|
|
47
|
+
dryRun: boolean;
|
|
40
48
|
inserter?: 'hidden' | 'visible';
|
|
41
49
|
slug?: string;
|
|
42
50
|
title?: string;
|
|
43
51
|
};
|
|
44
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
|
+
|
|
45
64
|
function parseArgs() {
|
|
46
65
|
const args = process.argv.slice( 2 );
|
|
47
66
|
const parsed: ParsedArgs = {
|
|
48
67
|
ancestorValues: [],
|
|
49
68
|
container: false,
|
|
69
|
+
dryRun: false,
|
|
50
70
|
};
|
|
51
71
|
|
|
52
72
|
for ( let index = 0; index < args.length; index += 1 ) {
|
|
@@ -86,6 +106,11 @@ function parseArgs() {
|
|
|
86
106
|
continue;
|
|
87
107
|
}
|
|
88
108
|
|
|
109
|
+
if ( arg === '--dry-run' ) {
|
|
110
|
+
parsed.dryRun = true;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
89
114
|
if ( arg === '--inserter' ) {
|
|
90
115
|
const value = args[ index + 1 ];
|
|
91
116
|
if ( value !== 'hidden' && value !== 'visible' ) {
|
|
@@ -95,6 +120,10 @@ function parseArgs() {
|
|
|
95
120
|
index += 1;
|
|
96
121
|
continue;
|
|
97
122
|
}
|
|
123
|
+
|
|
124
|
+
if ( arg.startsWith( '--' ) ) {
|
|
125
|
+
throw new Error( `Unknown option: ${ arg }.` );
|
|
126
|
+
}
|
|
98
127
|
}
|
|
99
128
|
|
|
100
129
|
return parsed;
|
|
@@ -395,16 +424,149 @@ function listExistingCompoundChildren(): ExistingCompoundChild[] {
|
|
|
395
424
|
return null;
|
|
396
425
|
}
|
|
397
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
|
+
|
|
398
458
|
return {
|
|
459
|
+
ancestorBlockNames,
|
|
399
460
|
blockJsonPath,
|
|
400
461
|
blockName,
|
|
462
|
+
container: Object.prototype.hasOwnProperty.call(
|
|
463
|
+
blockJson,
|
|
464
|
+
'allowedBlocks'
|
|
465
|
+
),
|
|
466
|
+
directAllowedBlocks,
|
|
401
467
|
folderSlug,
|
|
402
468
|
key: deriveChildKey( folderSlug ),
|
|
469
|
+
parentBlockNames,
|
|
470
|
+
placement: ancestorBlockNames.length > 0 ? 'nested' : 'root',
|
|
471
|
+
supportsInserter,
|
|
472
|
+
title,
|
|
403
473
|
} satisfies ExistingCompoundChild;
|
|
404
474
|
} )
|
|
405
475
|
.filter( ( child ): child is ExistingCompoundChild => child !== null );
|
|
406
476
|
}
|
|
407
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
|
+
|
|
408
570
|
function resolveExistingCompoundChild(
|
|
409
571
|
value: string,
|
|
410
572
|
existingChildren: ExistingCompoundChild[]
|
|
@@ -457,14 +619,8 @@ function validateAncestorChain(
|
|
|
457
619
|
for ( let index = 0; index < ancestorChain.length - 1; index += 1 ) {
|
|
458
620
|
const currentAncestor = ancestorChain[ index ];
|
|
459
621
|
const nextAncestor = ancestorChain[ index + 1 ];
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
? blockJson.allowedBlocks.filter(
|
|
463
|
-
( value ): value is string => typeof value === 'string'
|
|
464
|
-
)
|
|
465
|
-
: [];
|
|
466
|
-
|
|
467
|
-
if ( allowedBlocks.includes( nextAncestor.blockName ) ) {
|
|
622
|
+
|
|
623
|
+
if ( currentAncestor.directAllowedBlocks.includes( nextAncestor.blockName ) ) {
|
|
468
624
|
continue;
|
|
469
625
|
}
|
|
470
626
|
|
|
@@ -474,6 +630,22 @@ function validateAncestorChain(
|
|
|
474
630
|
}
|
|
475
631
|
}
|
|
476
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
|
+
|
|
477
649
|
function findCompoundChildSpecSource(
|
|
478
650
|
childrenRegistrySource: string,
|
|
479
651
|
childKey: string
|
|
@@ -528,6 +700,86 @@ function validateAncestorInstantiability(
|
|
|
528
700
|
}
|
|
529
701
|
}
|
|
530
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
|
+
|
|
531
783
|
function updateAllowedBlocks(
|
|
532
784
|
filePath: string,
|
|
533
785
|
blockName: string
|
|
@@ -756,8 +1008,7 @@ import { __ } from '@wordpress/i18n';
|
|
|
756
1008
|
|
|
757
1009
|
import metadata from './block-metadata';
|
|
758
1010
|
import {
|
|
759
|
-
\
|
|
760
|
-
\tgetChildTemplate,
|
|
1011
|
+
\tgetChildInnerBlocksPropsOptions,
|
|
761
1012
|
\thasNestedChildBlocks,
|
|
762
1013
|
} from '../${ PARENT_BLOCK_SLUG }/children';
|
|
763
1014
|
import { useTypiaValidation } from './hooks';
|
|
@@ -768,6 +1019,14 @@ import {
|
|
|
768
1019
|
} from './validators';
|
|
769
1020
|
|
|
770
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 >;
|
|
771
1030
|
|
|
772
1031
|
export default function Edit( {
|
|
773
1032
|
\tattributes,
|
|
@@ -778,8 +1037,9 @@ export default function Edit( {
|
|
|
778
1037
|
\t\tattributes,
|
|
779
1038
|
\t\tvalidate${ childInterfaceName }
|
|
780
1039
|
\t);
|
|
781
|
-
\tconst
|
|
782
|
-
\
|
|
1040
|
+
\tconst nestedInnerBlocksPropsOptions = getChildInnerBlocksPropsOptions(
|
|
1041
|
+
\t\tmetadata.name
|
|
1042
|
+
\t);
|
|
783
1043
|
\tconst showsNestedChildren = hasNestedChildBlocks( metadata.name );
|
|
784
1044
|
|
|
785
1045
|
\treturn (
|
|
@@ -809,11 +1069,8 @@ export default function Edit( {
|
|
|
809
1069
|
\t\t\t) }
|
|
810
1070
|
\t\t\t{ showsNestedChildren && (
|
|
811
1071
|
\t\t\t\t<div className="${ childCssClassName }__children">
|
|
812
|
-
\t\t\t\t\t<
|
|
813
|
-
\t\t\t\t\t\
|
|
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 }
|
|
1072
|
+
\t\t\t\t\t<TypedInnerBlocks
|
|
1073
|
+
\t\t\t\t\t\t{ ...( nestedInnerBlocksPropsOptions ?? {} ) }
|
|
817
1074
|
\t\t\t\t\t/>
|
|
818
1075
|
\t\t\t\t</div>
|
|
819
1076
|
\t\t\t) }
|
|
@@ -936,6 +1193,7 @@ function main() {
|
|
|
936
1193
|
const {
|
|
937
1194
|
ancestorValues,
|
|
938
1195
|
container,
|
|
1196
|
+
dryRun,
|
|
939
1197
|
inserter,
|
|
940
1198
|
slug,
|
|
941
1199
|
title,
|
|
@@ -947,6 +1205,7 @@ function main() {
|
|
|
947
1205
|
}
|
|
948
1206
|
|
|
949
1207
|
const existingChildren = listExistingCompoundChildren();
|
|
1208
|
+
validateExistingCompoundChildGraph( existingChildren );
|
|
950
1209
|
const ancestorChain = ensureUniqueAncestorChain(
|
|
951
1210
|
ancestorValues.map( ( value ) =>
|
|
952
1211
|
resolveExistingCompoundChild( value, existingChildren )
|
|
@@ -984,7 +1243,7 @@ function main() {
|
|
|
984
1243
|
}
|
|
985
1244
|
|
|
986
1245
|
validateAncestorInstantiability( childrenFile, ancestorChain );
|
|
987
|
-
|
|
1246
|
+
validateNestedAncestorTarget( ancestorChain );
|
|
988
1247
|
const supportsInserter =
|
|
989
1248
|
inserter === 'visible'
|
|
990
1249
|
? true
|
|
@@ -994,6 +1253,47 @@ function main() {
|
|
|
994
1253
|
const placement = ancestorChain.length > 0 ? 'nested' : 'root';
|
|
995
1254
|
const seedTemplate = supportsInserter || container || ancestorChain.length > 0;
|
|
996
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
|
+
}
|
|
997
1297
|
|
|
998
1298
|
fs.mkdirSync( childDir, { recursive: true } );
|
|
999
1299
|
|