@wp-typia/project-tools 0.11.1
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/README.md +32 -0
- package/dist/runtime/cli-add.d.ts +38 -0
- package/dist/runtime/cli-add.js +561 -0
- package/dist/runtime/cli-core.d.ts +25 -0
- package/dist/runtime/cli-core.js +25 -0
- package/dist/runtime/cli-doctor.d.ts +34 -0
- package/dist/runtime/cli-doctor.js +131 -0
- package/dist/runtime/cli-help.d.ts +9 -0
- package/dist/runtime/cli-help.js +37 -0
- package/dist/runtime/cli-prompt.d.ts +21 -0
- package/dist/runtime/cli-prompt.js +53 -0
- package/dist/runtime/cli-scaffold.d.ts +79 -0
- package/dist/runtime/cli-scaffold.js +206 -0
- package/dist/runtime/cli-templates.d.ts +30 -0
- package/dist/runtime/cli-templates.js +61 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/json-utils.d.ts +10 -0
- package/dist/runtime/json-utils.js +12 -0
- package/dist/runtime/local-dev-presets.d.ts +26 -0
- package/dist/runtime/local-dev-presets.js +132 -0
- package/dist/runtime/metadata-analysis.d.ts +11 -0
- package/dist/runtime/metadata-analysis.js +285 -0
- package/dist/runtime/metadata-model.d.ts +84 -0
- package/dist/runtime/metadata-model.js +59 -0
- package/dist/runtime/metadata-parser.d.ts +53 -0
- package/dist/runtime/metadata-parser.js +794 -0
- package/dist/runtime/metadata-php-render.d.ts +29 -0
- package/dist/runtime/metadata-php-render.js +549 -0
- package/dist/runtime/metadata-projection.d.ts +7 -0
- package/dist/runtime/metadata-projection.js +233 -0
- package/dist/runtime/migration-constants.d.ts +15 -0
- package/dist/runtime/migration-constants.js +16 -0
- package/dist/runtime/migration-diff.d.ts +2 -0
- package/dist/runtime/migration-diff.js +537 -0
- package/dist/runtime/migration-fixtures.d.ts +8 -0
- package/dist/runtime/migration-fixtures.js +94 -0
- package/dist/runtime/migration-fuzz-plan.d.ts +2 -0
- package/dist/runtime/migration-fuzz-plan.js +50 -0
- package/dist/runtime/migration-manifest.d.ts +19 -0
- package/dist/runtime/migration-manifest.js +129 -0
- package/dist/runtime/migration-project.d.ts +94 -0
- package/dist/runtime/migration-project.js +1101 -0
- package/dist/runtime/migration-render.d.ts +11 -0
- package/dist/runtime/migration-render.js +741 -0
- package/dist/runtime/migration-risk.d.ts +4 -0
- package/dist/runtime/migration-risk.js +52 -0
- package/dist/runtime/migration-types.d.ts +249 -0
- package/dist/runtime/migration-types.js +1 -0
- package/dist/runtime/migration-ui-capability.d.ts +17 -0
- package/dist/runtime/migration-ui-capability.js +190 -0
- package/dist/runtime/migration-utils.d.ts +69 -0
- package/dist/runtime/migration-utils.js +246 -0
- package/dist/runtime/migrations.d.ts +249 -0
- package/dist/runtime/migrations.js +1061 -0
- package/dist/runtime/object-utils.d.ts +12 -0
- package/dist/runtime/object-utils.js +14 -0
- package/dist/runtime/package-managers.d.ts +28 -0
- package/dist/runtime/package-managers.js +156 -0
- package/dist/runtime/package-versions.d.ts +10 -0
- package/dist/runtime/package-versions.js +68 -0
- package/dist/runtime/scaffold-onboarding.d.ts +32 -0
- package/dist/runtime/scaffold-onboarding.js +99 -0
- package/dist/runtime/scaffold.d.ts +146 -0
- package/dist/runtime/scaffold.js +612 -0
- package/dist/runtime/schema-core.d.ts +267 -0
- package/dist/runtime/schema-core.js +597 -0
- package/dist/runtime/starter-manifests.d.ts +25 -0
- package/dist/runtime/starter-manifests.js +383 -0
- package/dist/runtime/string-case.d.ts +36 -0
- package/dist/runtime/string-case.js +69 -0
- package/dist/runtime/template-builtins.d.ts +38 -0
- package/dist/runtime/template-builtins.js +72 -0
- package/dist/runtime/template-defaults.d.ts +75 -0
- package/dist/runtime/template-defaults.js +65 -0
- package/dist/runtime/template-registry.d.ts +36 -0
- package/dist/runtime/template-registry.js +94 -0
- package/dist/runtime/template-render.d.ts +24 -0
- package/dist/runtime/template-render.js +113 -0
- package/dist/runtime/template-source.d.ts +71 -0
- package/dist/runtime/template-source.js +821 -0
- package/dist/runtime/typia-tags.d.ts +1 -0
- package/dist/runtime/typia-tags.js +1 -0
- package/package.json +79 -0
- package/templates/_shared/base/languages/.gitkeep +1 -0
- package/templates/_shared/base/package.json.mustache +41 -0
- package/templates/_shared/base/scripts/sync-types-to-block-json.ts.mustache +118 -0
- package/templates/_shared/base/src/hooks.ts.mustache +19 -0
- package/templates/_shared/base/src/validator-toolkit.ts.mustache +31 -0
- package/templates/_shared/base/tsconfig.json.mustache +21 -0
- package/templates/_shared/base/webpack.config.js.mustache +99 -0
- package/templates/_shared/base/{{slugKebabCase}}.php.mustache +53 -0
- package/templates/_shared/compound/core/package.json.mustache +45 -0
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +559 -0
- package/templates/_shared/compound/core/scripts/block-config.ts.mustache +13 -0
- package/templates/_shared/compound/core/scripts/sync-types-to-block-json.ts.mustache +53 -0
- package/templates/_shared/compound/core/webpack.config.js.mustache +141 -0
- package/templates/_shared/compound/core/{{slugKebabCase}}.php.mustache +51 -0
- package/templates/_shared/compound/persistence/package.json.mustache +50 -0
- package/templates/_shared/compound/persistence/scripts/block-config.ts.mustache +59 -0
- package/templates/_shared/compound/persistence/scripts/sync-rest-contracts.ts.mustache +101 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api-types.ts.mustache +21 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api-validators.ts.mustache +32 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api.ts.mustache +68 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/block.json.mustache +52 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/data.ts.mustache +192 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/edit.tsx.mustache +123 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/hooks.ts.mustache +11 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/interactivity.ts.mustache +132 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/render.php.mustache +158 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/save.tsx.mustache +3 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/types.ts.mustache +56 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/validators.ts.mustache +32 -0
- package/templates/_shared/compound/persistence-auth/{{slugKebabCase}}.php.mustache +294 -0
- package/templates/_shared/compound/persistence-public/{{slugKebabCase}}.php.mustache +312 -0
- package/templates/_shared/migration-ui/common/src/admin/migration-dashboard.tsx +394 -0
- package/templates/_shared/migration-ui/common/src/migration-detector.ts +9 -0
- package/templates/_shared/migration-ui/common/src/migrations/helpers.ts +490 -0
- package/templates/_shared/migration-ui/common/src/migrations/index.ts +886 -0
- package/templates/_shared/persistence/auth/{{slugKebabCase}}.php.mustache +290 -0
- package/templates/_shared/persistence/core/package.json.mustache +46 -0
- package/templates/_shared/persistence/core/scripts/sync-rest-contracts.ts.mustache +113 -0
- package/templates/_shared/persistence/core/scripts/sync-types-to-block-json.ts.mustache +125 -0
- package/templates/_shared/persistence/core/src/api-types.ts.mustache +21 -0
- package/templates/_shared/persistence/core/src/api-validators.ts.mustache +32 -0
- package/templates/_shared/persistence/core/src/api.ts.mustache +68 -0
- package/templates/_shared/persistence/core/src/data.ts.mustache +192 -0
- package/templates/_shared/persistence/core/src/index.tsx.mustache +25 -0
- package/templates/_shared/persistence/core/src/interactivity.ts.mustache +134 -0
- package/templates/_shared/persistence/core/src/save.tsx.mustache +5 -0
- package/templates/_shared/persistence/core/src/validators.ts.mustache +32 -0
- package/templates/_shared/persistence/core/{{slugKebabCase}}.php.mustache +336 -0
- package/templates/_shared/persistence/public/{{slugKebabCase}}.php.mustache +308 -0
- package/templates/_shared/presets/test-preset/.wp-env.test.json.mustache +16 -0
- package/templates/_shared/presets/test-preset/playwright.config.ts.mustache +22 -0
- package/templates/_shared/presets/test-preset/scripts/wait-for-wp-env.mjs.mustache +102 -0
- package/templates/_shared/presets/test-preset/scripts/wp-env-utils.cjs.mustache +32 -0
- package/templates/_shared/presets/test-preset/tests/e2e/smoke.spec.ts.mustache +34 -0
- package/templates/_shared/presets/wp-env/.wp-env.json.mustache +16 -0
- package/templates/_shared/rest-helpers/auth/inc/rest-auth.php.mustache +37 -0
- package/templates/_shared/rest-helpers/public/inc/rest-public.php.mustache +314 -0
- package/templates/_shared/rest-helpers/shared/inc/rest-shared.php.mustache +58 -0
- package/templates/_shared/workspace/persistence-auth/inc/rest-auth.php.mustache +36 -0
- package/templates/_shared/workspace/persistence-auth/inc/rest-shared.php.mustache +55 -0
- package/templates/_shared/workspace/persistence-auth/server.php.mustache +237 -0
- package/templates/_shared/workspace/persistence-public/inc/rest-public.php.mustache +273 -0
- package/templates/_shared/workspace/persistence-public/inc/rest-shared.php.mustache +55 -0
- package/templates/_shared/workspace/persistence-public/server.php.mustache +252 -0
- package/templates/basic/src/block.json.mustache +51 -0
- package/templates/basic/src/edit.tsx.mustache +128 -0
- package/templates/basic/src/editor.scss.mustache +8 -0
- package/templates/basic/src/hooks.ts.mustache +18 -0
- package/templates/basic/src/index.tsx.mustache +45 -0
- package/templates/basic/src/save.tsx.mustache +30 -0
- package/templates/basic/src/style.scss.mustache +40 -0
- package/templates/basic/src/types.ts.mustache +56 -0
- package/templates/basic/src/validators.ts.mustache +26 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}/block.json.mustache +37 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}/children.ts.mustache +25 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}/edit.tsx.mustache +93 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}/hooks.ts.mustache +11 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}/index.tsx.mustache +25 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}/save.tsx.mustache +32 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}/style.scss.mustache +31 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}/types.ts.mustache +13 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}/validators.ts.mustache +17 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}-item/block.json.mustache +35 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}-item/edit.tsx.mustache +50 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}-item/hooks.ts.mustache +11 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}-item/index.tsx.mustache +25 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}-item/save.tsx.mustache +24 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}-item/types.ts.mustache +12 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}-item/validators.ts.mustache +17 -0
- package/templates/interactivity/package.json.mustache +42 -0
- package/templates/interactivity/src/block.json.mustache +73 -0
- package/templates/interactivity/src/edit.tsx.mustache +270 -0
- package/templates/interactivity/src/index.tsx.mustache +32 -0
- package/templates/interactivity/src/interactivity.ts.mustache +152 -0
- package/templates/interactivity/src/save.tsx.mustache +101 -0
- package/templates/interactivity/src/style.scss.mustache +60 -0
- package/templates/interactivity/src/types.ts.mustache +32 -0
- package/templates/interactivity/src/validators.ts.mustache +36 -0
- package/templates/persistence/src/block.json.mustache +52 -0
- package/templates/persistence/src/edit.tsx.mustache +165 -0
- package/templates/persistence/src/render.php.mustache +126 -0
- package/templates/persistence/src/style.scss.mustache +46 -0
- package/templates/persistence/src/types.ts.mustache +55 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const PARENT_BLOCK_NAME = '{{namespace}}/{{slugKebabCase}}';
|
|
6
|
+
const PARENT_BLOCK_NAMESPACE = '{{namespace}}';
|
|
7
|
+
const PARENT_BLOCK_SLUG = '{{slugKebabCase}}';
|
|
8
|
+
const PARENT_BLOCK_TITLE = {{titleJson}};
|
|
9
|
+
const PARENT_TYPE_NAME = '{{pascalCase}}';
|
|
10
|
+
const PARENT_STYLE_IMPORT = '../{{slugKebabCase}}/style.scss';
|
|
11
|
+
const PROJECT_ROOT = process.cwd();
|
|
12
|
+
const TEXT_DOMAIN = '{{textDomain}}';
|
|
13
|
+
|
|
14
|
+
const ALLOWED_CHILD_MARKER = '// add-child: insert new allowed child block names here';
|
|
15
|
+
const BLOCK_CONFIG_MARKER = '// add-child: insert new block config entries here';
|
|
16
|
+
const CHILD_PLACEHOLDER = 'Add supporting details for this internal item.';
|
|
17
|
+
const BLOCK_SLUG_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
18
|
+
|
|
19
|
+
type StarterManifestDocument = {
|
|
20
|
+
attributes: Record< string, unknown >;
|
|
21
|
+
manifestVersion: 2;
|
|
22
|
+
sourceType: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function parseArgs() {
|
|
26
|
+
const args = process.argv.slice( 2 );
|
|
27
|
+
const parsed: {
|
|
28
|
+
slug?: string;
|
|
29
|
+
title?: string;
|
|
30
|
+
} = {};
|
|
31
|
+
|
|
32
|
+
for ( let index = 0; index < args.length; index += 1 ) {
|
|
33
|
+
const arg = args[ index ];
|
|
34
|
+
if ( arg === '--slug' ) {
|
|
35
|
+
const value = args[ index + 1 ];
|
|
36
|
+
if ( ! value || value.startsWith( '--' ) ) {
|
|
37
|
+
throw new Error( '--slug requires a value.' );
|
|
38
|
+
}
|
|
39
|
+
parsed.slug = value;
|
|
40
|
+
index += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if ( arg === '--title' ) {
|
|
45
|
+
const value = args[ index + 1 ];
|
|
46
|
+
if ( ! value || value.startsWith( '--' ) ) {
|
|
47
|
+
throw new Error( '--title requires a value.' );
|
|
48
|
+
}
|
|
49
|
+
parsed.title = value;
|
|
50
|
+
index += 1;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function toKebabCase( input: string ): string {
|
|
59
|
+
return input
|
|
60
|
+
.trim()
|
|
61
|
+
.replace( /([a-z0-9])([A-Z])/g, '$1-$2' )
|
|
62
|
+
.replace( /[^A-Za-z0-9]+/g, '-' )
|
|
63
|
+
.replace( /^-+|-+$/g, '' )
|
|
64
|
+
.replace( /-{2,}/g, '-' )
|
|
65
|
+
.toLowerCase();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function toPascalCase( input: string ): string {
|
|
69
|
+
return toKebabCase( input )
|
|
70
|
+
.split( '-' )
|
|
71
|
+
.filter( Boolean )
|
|
72
|
+
.map( ( segment ) => segment.charAt( 0 ).toUpperCase() + segment.slice( 1 ) )
|
|
73
|
+
.join( '' );
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function toTitleCase( input: string ): string {
|
|
77
|
+
return toKebabCase( input )
|
|
78
|
+
.split( '-' )
|
|
79
|
+
.filter( Boolean )
|
|
80
|
+
.map( ( segment ) => segment.charAt( 0 ).toUpperCase() + segment.slice( 1 ) )
|
|
81
|
+
.join( ' ' );
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resolveValidatedNamespace( value: string ): string {
|
|
85
|
+
const normalizedNamespace = toKebabCase( value );
|
|
86
|
+
|
|
87
|
+
if ( ! BLOCK_SLUG_PATTERN.test( normalizedNamespace ) ) {
|
|
88
|
+
throw new Error( 'Use a block namespace with lowercase letters, numbers, and hyphens only.' );
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return normalizedNamespace;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function resolveValidatedBlockSlug( value: string ): string {
|
|
95
|
+
const normalizedSlug = toKebabCase( value );
|
|
96
|
+
|
|
97
|
+
if ( ! BLOCK_SLUG_PATTERN.test( normalizedSlug ) ) {
|
|
98
|
+
throw new Error( 'Use a child slug with lowercase letters, numbers, and hyphens only.' );
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return normalizedSlug;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function buildBlockCssClassName( namespace: string, slug: string ): string {
|
|
105
|
+
const normalizedSlug = resolveValidatedBlockSlug( slug );
|
|
106
|
+
const normalizedNamespace =
|
|
107
|
+
namespace.trim().length > 0
|
|
108
|
+
? resolveValidatedNamespace( namespace )
|
|
109
|
+
: '';
|
|
110
|
+
|
|
111
|
+
return normalizedNamespace.length > 0
|
|
112
|
+
? `wp-block-${ normalizedNamespace }-${ normalizedSlug }`
|
|
113
|
+
: `wp-block-${ normalizedSlug }`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function escapeForRegExp( value: string ): string {
|
|
117
|
+
return value.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function createStringStarterAttribute(
|
|
121
|
+
defaultValue: string,
|
|
122
|
+
maxLength: number
|
|
123
|
+
): Record< string, unknown > {
|
|
124
|
+
return {
|
|
125
|
+
ts: {
|
|
126
|
+
items: null,
|
|
127
|
+
kind: 'string',
|
|
128
|
+
properties: null,
|
|
129
|
+
required: true,
|
|
130
|
+
union: null,
|
|
131
|
+
},
|
|
132
|
+
typia: {
|
|
133
|
+
constraints: {
|
|
134
|
+
exclusiveMaximum: null,
|
|
135
|
+
exclusiveMinimum: null,
|
|
136
|
+
format: null,
|
|
137
|
+
maxItems: null,
|
|
138
|
+
maxLength,
|
|
139
|
+
maximum: null,
|
|
140
|
+
minItems: null,
|
|
141
|
+
minLength: 1,
|
|
142
|
+
minimum: null,
|
|
143
|
+
multipleOf: null,
|
|
144
|
+
pattern: null,
|
|
145
|
+
typeTag: null,
|
|
146
|
+
},
|
|
147
|
+
defaultValue,
|
|
148
|
+
hasDefault: true,
|
|
149
|
+
},
|
|
150
|
+
wp: {
|
|
151
|
+
defaultValue,
|
|
152
|
+
enum: null,
|
|
153
|
+
hasDefault: true,
|
|
154
|
+
type: 'string',
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function createCompoundChildStarterManifest(
|
|
160
|
+
childTypeName: string,
|
|
161
|
+
childTitle: string,
|
|
162
|
+
bodyPlaceholder = CHILD_PLACEHOLDER
|
|
163
|
+
): StarterManifestDocument {
|
|
164
|
+
return {
|
|
165
|
+
attributes: {
|
|
166
|
+
body: createStringStarterAttribute( bodyPlaceholder, 280 ),
|
|
167
|
+
title: createStringStarterAttribute( childTitle, 80 ),
|
|
168
|
+
},
|
|
169
|
+
manifestVersion: 2,
|
|
170
|
+
sourceType: childTypeName,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function stringifyStarterManifest( document: StarterManifestDocument ): string {
|
|
175
|
+
return `${ JSON.stringify( document, null, '\t' ) }\n`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function insertBeforeMarker( filePath: string, marker: string, insertionLines: string[] ) {
|
|
179
|
+
const source = fs.readFileSync( filePath, 'utf8' );
|
|
180
|
+
const markerPattern = new RegExp(
|
|
181
|
+
`^(?<indent>\\s*)${ escapeForRegExp( marker ) }\\s*$`,
|
|
182
|
+
'm'
|
|
183
|
+
);
|
|
184
|
+
const markerMatch = source.match( markerPattern );
|
|
185
|
+
|
|
186
|
+
if ( ! markerMatch ) {
|
|
187
|
+
throw new Error( `Unable to update ${ filePath }: marker not found.` );
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const indent = markerMatch.groups?.indent ?? '';
|
|
191
|
+
const markerLine = markerMatch[ 0 ];
|
|
192
|
+
const insertion = insertionLines
|
|
193
|
+
.map( ( line ) => ( line.length > 0 ? `${ indent }${ line }` : line ) )
|
|
194
|
+
.join( '\n' );
|
|
195
|
+
|
|
196
|
+
fs.writeFileSync(
|
|
197
|
+
filePath,
|
|
198
|
+
source.replace( markerPattern, `${ insertion }\n${ markerLine }` ),
|
|
199
|
+
'utf8'
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function renderBlockJson(
|
|
204
|
+
childBlockName: string,
|
|
205
|
+
childFolderSlug: string,
|
|
206
|
+
childTitle: string
|
|
207
|
+
): string {
|
|
208
|
+
const childCssClassName = buildBlockCssClassName( PARENT_BLOCK_NAMESPACE, childFolderSlug );
|
|
209
|
+
|
|
210
|
+
return `${ JSON.stringify(
|
|
211
|
+
{
|
|
212
|
+
$schema: 'https://schemas.wp.org/trunk/block.json',
|
|
213
|
+
apiVersion: 3,
|
|
214
|
+
name: childBlockName,
|
|
215
|
+
version: '{{blockMetadataVersion}}',
|
|
216
|
+
title: childTitle,
|
|
217
|
+
category: '{{compoundChildCategory}}',
|
|
218
|
+
icon: '{{compoundChildIcon}}',
|
|
219
|
+
description: `Internal item block used by ${ PARENT_BLOCK_TITLE }.`,
|
|
220
|
+
parent: [ PARENT_BLOCK_NAME ],
|
|
221
|
+
example: {},
|
|
222
|
+
supports: {
|
|
223
|
+
html: false,
|
|
224
|
+
inserter: false,
|
|
225
|
+
reusable: false,
|
|
226
|
+
},
|
|
227
|
+
attributes: {
|
|
228
|
+
title: {
|
|
229
|
+
type: 'string',
|
|
230
|
+
source: 'html',
|
|
231
|
+
selector: `.${ childCssClassName }__title`,
|
|
232
|
+
default: childTitle,
|
|
233
|
+
},
|
|
234
|
+
body: {
|
|
235
|
+
type: 'string',
|
|
236
|
+
source: 'html',
|
|
237
|
+
selector: `.${ childCssClassName }__body`,
|
|
238
|
+
default: CHILD_PLACEHOLDER,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
textdomain: TEXT_DOMAIN,
|
|
242
|
+
editorScript: 'file:./index.js',
|
|
243
|
+
},
|
|
244
|
+
null,
|
|
245
|
+
'\t'
|
|
246
|
+
) }\n`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function renderTypesFile( childTypeName: string, childTitle: string ): string {
|
|
250
|
+
return `import { tags } from 'typia';
|
|
251
|
+
|
|
252
|
+
export interface ${ childTypeName } {
|
|
253
|
+
\ttitle: string &
|
|
254
|
+
\t\ttags.MinLength< 1 > &
|
|
255
|
+
\t\ttags.MaxLength< 80 > &
|
|
256
|
+
\t\ttags.Default< ${ JSON.stringify( childTitle ) } >;
|
|
257
|
+
\tbody: string &
|
|
258
|
+
\t\ttags.MinLength< 1 > &
|
|
259
|
+
\t\ttags.MaxLength< 280 > &
|
|
260
|
+
\t\ttags.Default< ${ JSON.stringify( CHILD_PLACEHOLDER ) } >;
|
|
261
|
+
}
|
|
262
|
+
`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function renderStarterManifestFile(
|
|
266
|
+
childTypeName: string,
|
|
267
|
+
childTitle: string
|
|
268
|
+
): string {
|
|
269
|
+
return stringifyStarterManifest(
|
|
270
|
+
createCompoundChildStarterManifest(
|
|
271
|
+
childTypeName,
|
|
272
|
+
childTitle,
|
|
273
|
+
CHILD_PLACEHOLDER
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function renderHooksFile(): string {
|
|
279
|
+
return `import { useMemo } from '@wordpress/element';
|
|
280
|
+
|
|
281
|
+
import {
|
|
282
|
+
\tcreateUseTypiaValidationHook,
|
|
283
|
+
\tformatValidationError,
|
|
284
|
+
\tformatValidationErrors,
|
|
285
|
+
\ttype TypiaValidationError,
|
|
286
|
+
\ttype ValidationResult,
|
|
287
|
+
\ttype ValidationState,
|
|
288
|
+
} from '@wp-typia/block-runtime/validation';
|
|
289
|
+
|
|
290
|
+
export {
|
|
291
|
+
\tformatValidationError,
|
|
292
|
+
\tformatValidationErrors,
|
|
293
|
+
\ttype TypiaValidationError,
|
|
294
|
+
\ttype ValidationResult,
|
|
295
|
+
\ttype ValidationState,
|
|
296
|
+
} from '@wp-typia/block-runtime/validation';
|
|
297
|
+
|
|
298
|
+
export const useTypiaValidation = createUseTypiaValidationHook( {
|
|
299
|
+
\tuseMemo,
|
|
300
|
+
} );
|
|
301
|
+
`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function renderValidatorsFile(
|
|
305
|
+
childTypeName: string,
|
|
306
|
+
childInterfaceName: string
|
|
307
|
+
): string {
|
|
308
|
+
return `import typia from 'typia';
|
|
309
|
+
import currentManifest from './typia.manifest.json';
|
|
310
|
+
import {
|
|
311
|
+
\ttype ManifestDefaultsDocument,
|
|
312
|
+
} from '@wp-typia/block-runtime/defaults';
|
|
313
|
+
import {
|
|
314
|
+
\tcreateScaffoldValidatorToolkit,
|
|
315
|
+
} from '@wp-typia/block-runtime/validation';
|
|
316
|
+
|
|
317
|
+
import type { ${ childTypeName } } from './types';
|
|
318
|
+
|
|
319
|
+
const validate = typia.createValidate< ${ childTypeName } >();
|
|
320
|
+
const assert = typia.createAssert< ${ childTypeName } >();
|
|
321
|
+
const is = typia.createIs< ${ childTypeName } >();
|
|
322
|
+
const random = typia.createRandom< ${ childTypeName } >();
|
|
323
|
+
const clone = typia.misc.createClone< ${ childTypeName } >();
|
|
324
|
+
const prune = typia.misc.createPrune< ${ childTypeName } >();
|
|
325
|
+
const scaffoldValidators = createScaffoldValidatorToolkit< ${ childTypeName } >( {
|
|
326
|
+
\tmanifest: currentManifest as ManifestDefaultsDocument,
|
|
327
|
+
\tvalidate,
|
|
328
|
+
\tassert,
|
|
329
|
+
\tis,
|
|
330
|
+
\trandom,
|
|
331
|
+
\tclone,
|
|
332
|
+
\tprune,
|
|
333
|
+
} );
|
|
334
|
+
|
|
335
|
+
export const validate${ childInterfaceName } = scaffoldValidators.validateAttributes;
|
|
336
|
+
|
|
337
|
+
export const validators = scaffoldValidators.validators;
|
|
338
|
+
|
|
339
|
+
export const sanitize${ childInterfaceName } = scaffoldValidators.sanitizeAttributes;
|
|
340
|
+
|
|
341
|
+
export const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;
|
|
342
|
+
`;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function renderEditFile(
|
|
346
|
+
childFolderSlug: string,
|
|
347
|
+
childTypeName: string,
|
|
348
|
+
childInterfaceName: string,
|
|
349
|
+
childTitle: string
|
|
350
|
+
): string {
|
|
351
|
+
const childCssClassName = buildBlockCssClassName( PARENT_BLOCK_NAMESPACE, childFolderSlug );
|
|
352
|
+
|
|
353
|
+
return `import { RichText, useBlockProps } from '@wordpress/block-editor';
|
|
354
|
+
import { Notice } from '@wordpress/components';
|
|
355
|
+
import { __ } from '@wordpress/i18n';
|
|
356
|
+
|
|
357
|
+
import { useTypiaValidation } from './hooks';
|
|
358
|
+
import type { ${ childTypeName } } from './types';
|
|
359
|
+
import {
|
|
360
|
+
\tcreateAttributeUpdater,
|
|
361
|
+
\tvalidate${ childInterfaceName },
|
|
362
|
+
} from './validators';
|
|
363
|
+
|
|
364
|
+
export default function Edit( {
|
|
365
|
+
\tattributes,
|
|
366
|
+
\tsetAttributes,
|
|
367
|
+
}: {
|
|
368
|
+
\tattributes: ${ childTypeName };
|
|
369
|
+
\tsetAttributes: ( attrs: Partial< ${ childTypeName } > ) => void;
|
|
370
|
+
} ) {
|
|
371
|
+
\tconst updateAttribute = createAttributeUpdater( attributes, setAttributes );
|
|
372
|
+
\tconst { errorMessages, isValid } = useTypiaValidation(
|
|
373
|
+
\t\tattributes,
|
|
374
|
+
\t\tvalidate${ childInterfaceName }
|
|
375
|
+
\t);
|
|
376
|
+
|
|
377
|
+
\treturn (
|
|
378
|
+
\t\t<div { ...useBlockProps( { className: '${ childCssClassName }' } ) }>
|
|
379
|
+
\t\t\t<RichText
|
|
380
|
+
\t\t\t\ttagName="h4"
|
|
381
|
+
\t\t\t\tclassName="${ childCssClassName }__title"
|
|
382
|
+
\t\t\t\tvalue={ attributes.title ?? '' }
|
|
383
|
+
\t\t\t\tonChange={ ( title ) => updateAttribute( 'title', title ) }
|
|
384
|
+
\t\t\t\tplaceholder={ __( ${ JSON.stringify( childTitle ) }, '${ TEXT_DOMAIN }' ) }
|
|
385
|
+
\t\t\t/>
|
|
386
|
+
\t\t\t<RichText
|
|
387
|
+
\t\t\t\ttagName="p"
|
|
388
|
+
\t\t\t\tclassName="${ childCssClassName }__body"
|
|
389
|
+
\t\t\t\tvalue={ attributes.body ?? '' }
|
|
390
|
+
\t\t\t\tonChange={ ( body ) => updateAttribute( 'body', body ) }
|
|
391
|
+
\t\t\t\tplaceholder={ __( ${ JSON.stringify( CHILD_PLACEHOLDER ) }, '${ TEXT_DOMAIN }' ) }
|
|
392
|
+
\t\t\t/>
|
|
393
|
+
\t\t\t{ ! isValid && (
|
|
394
|
+
\t\t\t\t<Notice status="error" isDismissible={ false }>
|
|
395
|
+
\t\t\t\t\t<ul>
|
|
396
|
+
\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => (
|
|
397
|
+
\t\t\t\t\t\t\t<li key={ index }>{ error }</li>
|
|
398
|
+
\t\t\t\t\t\t) ) }
|
|
399
|
+
\t\t\t\t\t</ul>
|
|
400
|
+
\t\t\t\t</Notice>
|
|
401
|
+
\t\t\t) }
|
|
402
|
+
\t\t</div>
|
|
403
|
+
\t);
|
|
404
|
+
}
|
|
405
|
+
`;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function renderSaveFile( childFolderSlug: string, childTypeName: string ): string {
|
|
409
|
+
const childCssClassName = buildBlockCssClassName( PARENT_BLOCK_NAMESPACE, childFolderSlug );
|
|
410
|
+
|
|
411
|
+
return `import { RichText, useBlockProps } from '@wordpress/block-editor';
|
|
412
|
+
|
|
413
|
+
import type { ${ childTypeName } } from './types';
|
|
414
|
+
|
|
415
|
+
export default function Save( {
|
|
416
|
+
\tattributes,
|
|
417
|
+
}: {
|
|
418
|
+
\tattributes: ${ childTypeName };
|
|
419
|
+
} ) {
|
|
420
|
+
\treturn (
|
|
421
|
+
\t\t<div { ...useBlockProps.save( { className: '${ childCssClassName }' } ) }>
|
|
422
|
+
\t\t\t<RichText.Content
|
|
423
|
+
\t\t\t\ttagName="h4"
|
|
424
|
+
\t\t\t\tclassName="${ childCssClassName }__title"
|
|
425
|
+
\t\t\t\tvalue={ attributes.title }
|
|
426
|
+
\t\t\t/>
|
|
427
|
+
\t\t\t<RichText.Content
|
|
428
|
+
\t\t\t\ttagName="p"
|
|
429
|
+
\t\t\t\tclassName="${ childCssClassName }__body"
|
|
430
|
+
\t\t\t\tvalue={ attributes.body }
|
|
431
|
+
\t\t\t/>
|
|
432
|
+
\t\t</div>
|
|
433
|
+
\t);
|
|
434
|
+
}
|
|
435
|
+
`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function renderIndexFile( childTypeName: string, childFolderSlug: string ): string {
|
|
439
|
+
return `import { registerBlockType } from '@wordpress/blocks';
|
|
440
|
+
import type { BlockConfiguration } from '@wordpress/blocks';
|
|
441
|
+
import {
|
|
442
|
+
\tbuildScaffoldBlockRegistration,
|
|
443
|
+
\ttype ScaffoldBlockMetadata,
|
|
444
|
+
} from '@wp-typia/block-runtime/blocks';
|
|
445
|
+
|
|
446
|
+
import Edit from './edit';
|
|
447
|
+
import Save from './save';
|
|
448
|
+
import metadata from './block.json';
|
|
449
|
+
import '${ PARENT_STYLE_IMPORT }';
|
|
450
|
+
|
|
451
|
+
import type { ${ childTypeName } } from './types';
|
|
452
|
+
|
|
453
|
+
const registration = buildScaffoldBlockRegistration<
|
|
454
|
+
\tBlockConfiguration< ${ childTypeName } >
|
|
455
|
+
>( metadata as ScaffoldBlockMetadata, {
|
|
456
|
+
\tedit: Edit,
|
|
457
|
+
\tsave: Save,
|
|
458
|
+
} );
|
|
459
|
+
|
|
460
|
+
registerBlockType< ${ childTypeName } >(
|
|
461
|
+
\tregistration.name,
|
|
462
|
+
\tregistration.settings
|
|
463
|
+
);
|
|
464
|
+
`;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function main() {
|
|
468
|
+
const { slug, title } = parseArgs();
|
|
469
|
+
const normalizedSlug = slug ? resolveValidatedBlockSlug( slug ) : '';
|
|
470
|
+
|
|
471
|
+
if ( normalizedSlug.length === 0 ) {
|
|
472
|
+
throw new Error( 'Use a child slug with lowercase letters, numbers, and hyphens only.' );
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const childTitle = title?.trim().length ? title.trim() : toTitleCase( normalizedSlug );
|
|
476
|
+
const childFolderSlug = `${ PARENT_BLOCK_SLUG }-${ normalizedSlug }`;
|
|
477
|
+
const childBlockName = `${ PARENT_BLOCK_NAME }-${ normalizedSlug }`;
|
|
478
|
+
const childTypeSuffix = toPascalCase( normalizedSlug );
|
|
479
|
+
const childTypeName = `${ PARENT_TYPE_NAME }${ childTypeSuffix }Attributes`;
|
|
480
|
+
const childInterfaceName = `${ PARENT_TYPE_NAME }${ childTypeSuffix }`;
|
|
481
|
+
const childDir = path.join( PROJECT_ROOT, 'src', 'blocks', childFolderSlug );
|
|
482
|
+
const childrenFile = path.join(
|
|
483
|
+
PROJECT_ROOT,
|
|
484
|
+
'src',
|
|
485
|
+
'blocks',
|
|
486
|
+
PARENT_BLOCK_SLUG,
|
|
487
|
+
'children.ts'
|
|
488
|
+
);
|
|
489
|
+
const blockConfigFile = path.join( PROJECT_ROOT, 'scripts', 'block-config.ts' );
|
|
490
|
+
|
|
491
|
+
if ( fs.existsSync( childDir ) ) {
|
|
492
|
+
throw new Error( `Child block already exists: ${ childFolderSlug }` );
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if ( ! fs.existsSync( childrenFile ) || ! fs.existsSync( blockConfigFile ) ) {
|
|
496
|
+
throw new Error(
|
|
497
|
+
'This command expects a compound scaffold with src/blocks/<parent>/children.ts and scripts/block-config.ts.'
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
fs.mkdirSync( childDir, { recursive: true } );
|
|
502
|
+
|
|
503
|
+
fs.writeFileSync(
|
|
504
|
+
path.join( childDir, 'block.json' ),
|
|
505
|
+
renderBlockJson( childBlockName, childFolderSlug, childTitle ),
|
|
506
|
+
'utf8'
|
|
507
|
+
);
|
|
508
|
+
fs.writeFileSync( path.join( childDir, 'types.ts' ), renderTypesFile( childTypeName, childTitle ), 'utf8' );
|
|
509
|
+
fs.writeFileSync(
|
|
510
|
+
path.join( childDir, 'typia.manifest.json' ),
|
|
511
|
+
renderStarterManifestFile( childTypeName, childTitle ),
|
|
512
|
+
'utf8'
|
|
513
|
+
);
|
|
514
|
+
fs.writeFileSync( path.join( childDir, 'hooks.ts' ), renderHooksFile(), 'utf8' );
|
|
515
|
+
fs.writeFileSync(
|
|
516
|
+
path.join( childDir, 'validators.ts' ),
|
|
517
|
+
renderValidatorsFile( childTypeName, childInterfaceName ),
|
|
518
|
+
'utf8'
|
|
519
|
+
);
|
|
520
|
+
fs.writeFileSync(
|
|
521
|
+
path.join( childDir, 'edit.tsx' ),
|
|
522
|
+
renderEditFile( childFolderSlug, childTypeName, childInterfaceName, childTitle ),
|
|
523
|
+
'utf8'
|
|
524
|
+
);
|
|
525
|
+
fs.writeFileSync(
|
|
526
|
+
path.join( childDir, 'save.tsx' ),
|
|
527
|
+
renderSaveFile( childFolderSlug, childTypeName ),
|
|
528
|
+
'utf8'
|
|
529
|
+
);
|
|
530
|
+
fs.writeFileSync(
|
|
531
|
+
path.join( childDir, 'index.tsx' ),
|
|
532
|
+
renderIndexFile( childTypeName, childFolderSlug ),
|
|
533
|
+
'utf8'
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
insertBeforeMarker(
|
|
537
|
+
childrenFile,
|
|
538
|
+
ALLOWED_CHILD_MARKER,
|
|
539
|
+
[ `'${ childBlockName }',` ]
|
|
540
|
+
);
|
|
541
|
+
insertBeforeMarker(
|
|
542
|
+
blockConfigFile,
|
|
543
|
+
BLOCK_CONFIG_MARKER,
|
|
544
|
+
[
|
|
545
|
+
'{',
|
|
546
|
+
`\tslug: '${ childFolderSlug }',`,
|
|
547
|
+
`\tattributeTypeName: '${ childTypeName }',`,
|
|
548
|
+
`\ttypesFile: 'src/blocks/${ childFolderSlug }/types.ts',`,
|
|
549
|
+
'},',
|
|
550
|
+
]
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
console.log( `✅ Added compound child block ${ childBlockName }` );
|
|
554
|
+
console.log(
|
|
555
|
+
'Run `sync-types` next to generate block.json metadata, manifests, schemas, and PHP validators for the new child block.'
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
main();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const BLOCKS = [
|
|
2
|
+
{
|
|
3
|
+
attributeTypeName: '{{pascalCase}}Attributes',
|
|
4
|
+
slug: '{{slugKebabCase}}',
|
|
5
|
+
typesFile: 'src/blocks/{{slugKebabCase}}/types.ts',
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
slug: '{{slugKebabCase}}-item',
|
|
9
|
+
attributeTypeName: '{{pascalCase}}ItemAttributes',
|
|
10
|
+
typesFile: 'src/blocks/{{slugKebabCase}}-item/types.ts',
|
|
11
|
+
},
|
|
12
|
+
// add-child: insert new block config entries here
|
|
13
|
+
] as const;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { syncBlockMetadata } from '@wp-typia/block-runtime/metadata-core';
|
|
5
|
+
|
|
6
|
+
import { BLOCKS } from './block-config';
|
|
7
|
+
|
|
8
|
+
function parseCliOptions( argv: string[] ) {
|
|
9
|
+
const options = {
|
|
10
|
+
check: false,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
for ( const argument of argv ) {
|
|
14
|
+
if ( argument === '--check' ) {
|
|
15
|
+
options.check = true;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
throw new Error( `Unknown sync-types flag: ${ argument }` );
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return options;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
const options = parseCliOptions( process.argv.slice( 2 ) );
|
|
27
|
+
|
|
28
|
+
for ( const block of BLOCKS ) {
|
|
29
|
+
const baseDir = path.join( 'src', 'blocks', block.slug );
|
|
30
|
+
const result = await syncBlockMetadata( {
|
|
31
|
+
blockJsonFile: path.join( baseDir, 'block.json' ),
|
|
32
|
+
jsonSchemaFile: path.join( baseDir, 'typia.schema.json' ),
|
|
33
|
+
manifestFile: path.join( baseDir, 'typia.manifest.json' ),
|
|
34
|
+
openApiFile: path.join( baseDir, 'typia.openapi.json' ),
|
|
35
|
+
sourceTypeName: block.attributeTypeName,
|
|
36
|
+
typesFile: block.typesFile,
|
|
37
|
+
}, {
|
|
38
|
+
check: options.check,
|
|
39
|
+
} );
|
|
40
|
+
|
|
41
|
+
console.log(
|
|
42
|
+
options.check
|
|
43
|
+
? `✅ ${ block.slug }: block.json, typia.manifest.json, typia-validator.php, typia.schema.json, and typia.openapi.json are already up to date with the TypeScript types!`
|
|
44
|
+
: `✅ ${ block.slug }: block.json, typia.manifest.json, typia-validator.php, typia.schema.json, and typia.openapi.json were generated from TypeScript types!`
|
|
45
|
+
);
|
|
46
|
+
console.log( '📝 Generated attributes:', result.attributeNames );
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
main().catch( ( error ) => {
|
|
51
|
+
console.error( '❌ Type sync failed:', error );
|
|
52
|
+
process.exit( 1 );
|
|
53
|
+
} );
|