@wp-typia/create-workspace-template 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,16 +16,39 @@ npm run wp-typia:add -- block my-block --template basic
16
16
  npm run wp-typia:add -- integration-env local-smoke --wp-env --release-zip --service docker-compose
17
17
  npm run wp-typia:add -- style callout-emphasis --block my-block
18
18
  npm run wp-typia:add -- transform quote-to-block --from core/quote --to my-block
19
+ npm run wp-typia:add -- pattern hero-layout
20
+ npm run wp-typia:add -- pattern hero-photo-section --scope section --section-role hero --tags hero,image
19
21
  npm run wp-typia:add -- binding-source hero-data
20
22
  npm run wp-typia:add -- binding-source hero-data --block my-block --attribute headline
21
23
  npm run wp-typia:add -- contract external-retrieve-response --type ExternalRetrieveResponse
22
24
  npm run wp-typia:add -- rest-resource external-record --manual --namespace legacy/v1 --route-pattern '/records/(?P<post_id>[\d]+)' --permission-callback legacy_can_read_records --controller-class Legacy\\Records\\Controller
23
25
  npm run wp-typia:add -- post-meta integration-state --post-type post --type IntegrationStateMeta
26
+ npm run wp-typia:add -- binding-source integration-state-source --from-post-meta integration-state --meta-path status --block my-block --attribute headline
24
27
  npm run wp-typia:add -- editor-plugin review-workflow --slot sidebar
25
28
  npm run wp-typia:add -- editor-plugin seo-notes --slot document-setting-panel
26
29
  npm run wp-typia:add -- hooked-block my-block --anchor core/post-content --position after
27
30
  ```
28
31
 
32
+ Typed block nesting rules live in `BLOCK_NESTING` inside
33
+ `scripts/block-config.ts`. Declare `parent`, `ancestor`, or `allowedBlocks`
34
+ relationships there and `wp-typia sync --check` will validate referenced block
35
+ names in the workspace namespace while allowing external targets like
36
+ `core/group`, then keep the matching `block.json` metadata current.
37
+ Declare default editor `InnerBlocks` templates in `BLOCK_TEMPLATES` and
38
+ `wp-typia sync` will generate `src/inner-blocks-templates.ts` with constants you
39
+ can import from edit components.
40
+ Pattern files listed in `PATTERNS` are parsed during the same sync flow so
41
+ `wp-typia sync --check` can first validate typed catalog metadata such as
42
+ duplicate slugs, `scope`, `sectionRole`, `tags`, `thumbnailUrl`, and
43
+ `contentFile`, then catch serialized block content that violates the declared
44
+ `allowedBlocks`, `parent`, or `ancestor` rules. Unknown or unparseable pattern
45
+ blocks are reported as warnings so the first implementation stays non-mutating.
46
+ The validator reads serialized `<!-- wp:* -->` block comment boundaries
47
+ conservatively and does not execute dynamic PHP content.
48
+ For a complete generic family with a container, section, title, body, and media
49
+ block, see the Nesting Contracts Guide in the hosted docs and the checked
50
+ fixture at `tests/fixtures/nested-block-family.ts`.
51
+
29
52
  ## CLI binary policy
30
53
 
31
54
  Official workspace projects install `wp-typia` as a local devDependency and
@@ -38,11 +38,33 @@ bun run wp-typia:add variation hero-card --block counter-card
38
38
  bun run wp-typia:add style callout-emphasis --block counter-card
39
39
  bun run wp-typia:add transform quote-to-counter --from core/quote --to counter-card
40
40
  bun run wp-typia:add pattern hero-layout
41
+ bun run wp-typia:add pattern hero-photo-section --scope section --section-role hero --tags hero,image
41
42
  bun run wp-typia:add rest-resource snapshots --namespace {{namespace}}/v1 --methods list,read,create
42
43
  bun run wp-typia:add rest-resource external-record --manual --namespace legacy/v1 --route-pattern '/records/(?P<post_id>[\d]+)' --permission-callback legacy_can_read_records --controller-class Legacy\\Records\\Controller
43
44
  bun run wp-typia:add post-meta integration-state --post-type post
45
+ bun run wp-typia:add binding-source integration-state-source --from-post-meta integration-state --meta-path status --block counter-card --attribute headline
44
46
  ```
45
47
 
48
+ Typed block nesting rules live in `BLOCK_NESTING` inside
49
+ `scripts/block-config.ts`. Declare `parent`, `ancestor`, or `allowedBlocks`
50
+ relationships there and `wp-typia sync --check` will validate referenced block
51
+ names in the workspace namespace while allowing external targets like
52
+ `core/group`, then keep the matching `block.json` metadata current.
53
+ Declare default editor `InnerBlocks` templates in `BLOCK_TEMPLATES` and
54
+ `wp-typia sync` will generate `src/inner-blocks-templates.ts` with constants you
55
+ can import from edit components.
56
+ Pattern files listed in `PATTERNS` are parsed during the same sync flow so
57
+ `wp-typia sync --check` can first validate typed catalog metadata such as
58
+ duplicate slugs, `scope`, `sectionRole`, `tags`, `thumbnailUrl`, and
59
+ `contentFile`, then catch serialized block content that violates the declared
60
+ `allowedBlocks`, `parent`, or `ancestor` rules. Unknown or unparseable pattern
61
+ blocks are reported as warnings so the first implementation stays non-mutating.
62
+ The validator reads serialized `<!-- wp:* -->` block comment boundaries
63
+ conservatively and does not execute dynamic PHP content.
64
+ For a complete generic family with a container, section, title, body, and media
65
+ block, see the Nesting Contracts Guide in the hosted docs and the checked
66
+ fixture at `tests/fixtures/nested-block-family.ts`.
67
+
46
68
  ## CLI Commands
47
69
 
48
70
  This workspace installs the executable `wp-typia` CLI as a local devDependency
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wp-typia/create-workspace-template",
3
- "version": "0.17.1",
3
+ "version": "0.18.0",
4
4
  "description": "Official empty wp-typia workspace template package",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "type": "module",
@@ -1,3 +1,8 @@
1
+ import {
2
+ defineBlockNesting,
3
+ defineInnerBlocksTemplates,
4
+ } from '@wp-typia/block-runtime/metadata-core';
5
+
1
6
  export interface WorkspaceBlockConfig {
2
7
  slug: string;
3
8
  attributeTypeName: string;
@@ -9,6 +14,14 @@ export interface WorkspaceBlockConfig {
9
14
  >;
10
15
  }
11
16
 
17
+ export const BLOCK_NESTING = defineBlockNesting( {
18
+ // Add parent, ancestor, and allowedBlocks relationships here.
19
+ } );
20
+
21
+ export const BLOCK_TEMPLATES = defineInnerBlocksTemplates( {
22
+ // Add default InnerBlocks templates here.
23
+ } );
24
+
12
25
  export interface WorkspaceVariationConfig {
13
26
  block: string;
14
27
  file: string;
@@ -30,14 +43,22 @@ export interface WorkspaceBlockTransformConfig {
30
43
  }
31
44
 
32
45
  export interface WorkspacePatternConfig {
33
- file: string;
46
+ contentFile?: string;
47
+ file?: string;
48
+ scope?: 'full' | 'section';
49
+ sectionRole?: string;
34
50
  slug: string;
51
+ tags?: string[];
52
+ thumbnailUrl?: string;
53
+ title?: string;
35
54
  }
36
55
 
37
56
  export interface WorkspaceBindingSourceConfig {
38
57
  attribute?: string;
39
58
  block?: string;
40
59
  editorFile: string;
60
+ metaPath?: string;
61
+ postMeta?: string;
41
62
  serverFile: string;
42
63
  slug: string;
43
64
  }
@@ -1,9 +1,22 @@
1
1
  /* eslint-disable no-console */
2
+ import fs from 'node:fs';
2
3
  import path from 'node:path';
3
4
 
4
- import { syncBlockMetadata } from '@wp-typia/block-runtime/metadata-core';
5
+ import {
6
+ formatBlockPatternContentNestingDiagnostics,
7
+ validateBlockPatternContentNesting,
8
+ syncInnerBlocksTemplateModule,
9
+ syncBlockMetadata,
10
+ validateBlockNestingContract,
11
+ validateInnerBlocksTemplates,
12
+ } from '@wp-typia/block-runtime/metadata-core';
13
+ import {
14
+ formatPatternCatalogDiagnostics,
15
+ resolvePatternCatalogContentFile,
16
+ validatePatternCatalog,
17
+ } from '@wp-typia/project-tools/pattern-catalog';
5
18
 
6
- import { BLOCKS } from './block-config';
19
+ import { BLOCK_NESTING, BLOCK_TEMPLATES, BLOCKS, PATTERNS } from './block-config';
7
20
 
8
21
  function parseCliOptions( argv: string[] ) {
9
22
  const options = {
@@ -22,9 +35,129 @@ function parseCliOptions( argv: string[] ) {
22
35
  return options;
23
36
  }
24
37
 
38
+ function readWorkspaceBlockName( block: ( typeof BLOCKS )[ number ] ): string {
39
+ const blockJsonFile = path.join( 'src', 'blocks', block.slug, 'block.json' );
40
+ let blockJson: { name?: unknown };
41
+ try {
42
+ blockJson = JSON.parse( fs.readFileSync( blockJsonFile, 'utf8' ) ) as {
43
+ name?: unknown;
44
+ };
45
+ } catch ( error ) {
46
+ throw new Error(
47
+ `Failed to read block.json for block "${ block.slug }" at ${ blockJsonFile }: ${
48
+ error instanceof Error ? error.message : String( error )
49
+ }`
50
+ );
51
+ }
52
+ if ( typeof blockJson.name !== 'string' || blockJson.name.trim() === '' ) {
53
+ throw new Error( `${ blockJsonFile } must define a string "name".` );
54
+ }
55
+
56
+ return blockJson.name;
57
+ }
58
+
59
+ function readPatternFileContent( pattern: ( typeof PATTERNS )[ number ] ): string {
60
+ const patternFile = resolvePatternCatalogContentFile( pattern );
61
+ if ( ! patternFile ) {
62
+ throw new Error(
63
+ `Pattern "${ pattern.slug }" must declare contentFile or file in scripts/block-config.ts.`
64
+ );
65
+ }
66
+ try {
67
+ return fs.readFileSync( patternFile, 'utf8' );
68
+ } catch ( error ) {
69
+ throw new Error(
70
+ `Failed to read pattern "${ pattern.slug }" at ${ patternFile }: ${
71
+ error instanceof Error ? error.message : String( error )
72
+ }`
73
+ );
74
+ }
75
+ }
76
+
77
+ function validatePatternCatalogMetadata() {
78
+ const result = validatePatternCatalog( PATTERNS, {
79
+ projectDir: process.cwd(),
80
+ } );
81
+
82
+ if ( result.warnings.length > 0 ) {
83
+ console.warn(
84
+ `⚠️ Pattern catalog validation warnings:\n${ formatPatternCatalogDiagnostics(
85
+ result.warnings
86
+ ) }`
87
+ );
88
+ }
89
+
90
+ if ( result.errors.length > 0 ) {
91
+ throw new Error(
92
+ `Pattern catalog metadata is invalid:\n${ formatPatternCatalogDiagnostics(
93
+ result.errors
94
+ ) }`
95
+ );
96
+ }
97
+ }
98
+
99
+ function validatePatternContentNesting( knownBlockNames: readonly string[] ) {
100
+ const diagnostics = PATTERNS.flatMap( ( pattern ) => {
101
+ const content = readPatternFileContent( pattern );
102
+ return validateBlockPatternContentNesting( content, {
103
+ allowExternalBlockNames: true,
104
+ knownBlockNames,
105
+ nesting: BLOCK_NESTING,
106
+ patternFile: resolvePatternCatalogContentFile( pattern ),
107
+ } ).diagnostics;
108
+ } );
109
+ const warnings = diagnostics.filter(
110
+ ( diagnostic ) => diagnostic.severity === 'warning'
111
+ );
112
+ const errors = diagnostics.filter(
113
+ ( diagnostic ) => diagnostic.severity === 'error'
114
+ );
115
+
116
+ if ( warnings.length > 0 ) {
117
+ console.warn(
118
+ `⚠️ Pattern nesting validation warnings:\n${ formatBlockPatternContentNestingDiagnostics(
119
+ warnings
120
+ ) }`
121
+ );
122
+ }
123
+
124
+ if ( errors.length > 0 ) {
125
+ throw new Error(
126
+ `Pattern content violates block nesting contract:\n${ formatBlockPatternContentNestingDiagnostics(
127
+ errors
128
+ ) }`
129
+ );
130
+ }
131
+ }
132
+
25
133
  async function main() {
26
134
  const options = parseCliOptions( process.argv.slice( 2 ) );
27
135
 
136
+ const knownBlockNames = BLOCKS.map( readWorkspaceBlockName );
137
+ validateBlockNestingContract( BLOCK_NESTING, {
138
+ allowExternalBlockNames: true,
139
+ knownBlockNames,
140
+ } );
141
+ validateInnerBlocksTemplates( BLOCK_TEMPLATES, {
142
+ allowExternalBlockNames: true,
143
+ knownBlockNames,
144
+ nesting: BLOCK_NESTING,
145
+ } );
146
+ validatePatternCatalogMetadata();
147
+ validatePatternContentNesting( knownBlockNames );
148
+ await syncInnerBlocksTemplateModule(
149
+ {
150
+ allowExternalBlockNames: true,
151
+ knownBlockNames,
152
+ nesting: BLOCK_NESTING,
153
+ outputFile: path.join( 'src', 'inner-blocks-templates.ts' ),
154
+ templates: BLOCK_TEMPLATES,
155
+ },
156
+ {
157
+ check: options.check,
158
+ }
159
+ );
160
+
28
161
  if ( BLOCKS.length === 0 ) {
29
162
  console.log(
30
163
  options.check
@@ -38,9 +171,12 @@ async function main() {
38
171
  const baseDir = path.join( 'src', 'blocks', block.slug );
39
172
  const result = await syncBlockMetadata(
40
173
  {
174
+ allowExternalBlockNames: true,
41
175
  blockJsonFile: path.join( baseDir, 'block.json' ),
42
176
  jsonSchemaFile: path.join( baseDir, 'typia.schema.json' ),
177
+ knownBlockNames,
43
178
  manifestFile: path.join( baseDir, 'typia.manifest.json' ),
179
+ nesting: BLOCK_NESTING,
44
180
  openApiFile: path.join( baseDir, 'typia.openapi.json' ),
45
181
  sourceTypeName: block.attributeTypeName,
46
182
  typesFile: block.typesFile,
@@ -0,0 +1,13 @@
1
+ /* This file is generated by wp-typia. Do not edit manually. */
2
+
3
+ export type WpTypiaInnerBlocksTemplateAttributes = Record<string, unknown>;
4
+ export type WpTypiaInnerBlocksTemplateItem = [
5
+ blockName: string,
6
+ attributes?: WpTypiaInnerBlocksTemplateAttributes,
7
+ innerBlocks?: WpTypiaInnerBlocksTemplate,
8
+ ];
9
+ export type WpTypiaInnerBlocksTemplate = WpTypiaInnerBlocksTemplateItem[];
10
+
11
+ export const INNER_BLOCKS_TEMPLATES = {} satisfies Record<string, WpTypiaInnerBlocksTemplate>;
12
+
13
+ export type WpTypiaInnerBlocksTemplateName = keyof typeof INNER_BLOCKS_TEMPLATES;
@@ -177,7 +177,11 @@ function {{phpPrefix}}_register_pattern_category() {
177
177
  }
178
178
 
179
179
  function {{phpPrefix}}_register_patterns() {
180
- foreach ( glob( __DIR__ . '/src/patterns/*.php' ) ?: array() as $pattern_module ) {
180
+ $pattern_modules = array_merge(
181
+ glob( __DIR__ . '/src/patterns/*.php' ) ?: array(),
182
+ glob( __DIR__ . '/src/patterns/*/*.php' ) ?: array()
183
+ );
184
+ foreach ( $pattern_modules as $pattern_module ) {
181
185
  require $pattern_module;
182
186
  }
183
187
  }