From 29b129eb71bdc92fd786613635fec260560b8e69 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Nov 2025 09:27:42 +0000 Subject: [PATCH 1/9] Initial plan From a337f73d0484340dc073dcc0fc8eb2f8bf267987 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Nov 2025 09:36:59 +0000 Subject: [PATCH 2/9] Add block and pattern commands with WordPress 5.0+ support Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- composer.json | 10 ++ entity-command.php | 31 ++++++ src/Block_Command.php | 219 ++++++++++++++++++++++++++++++++++++++++ src/Pattern_Command.php | 211 ++++++++++++++++++++++++++++++++++++++ src/Post_Command.php | 125 +++++++++++++++++++++++ 5 files changed, 596 insertions(+) create mode 100644 src/Block_Command.php create mode 100644 src/Pattern_Command.php diff --git a/composer.json b/composer.json index 21d853c5..67b00871 100644 --- a/composer.json +++ b/composer.json @@ -63,6 +63,9 @@ "comment unspam", "comment untrash", "comment update", + "block", + "block get", + "block list", "menu", "menu create", "menu delete", @@ -96,6 +99,9 @@ "option update", "option set-autoload", "option get-autoload", + "pattern", + "pattern get", + "pattern list", "post", "post create", "post delete", @@ -103,6 +109,8 @@ "post exists", "post generate", "post get", + "post has-block", + "post has-blocks", "post list", "post meta", "post meta add", @@ -113,6 +121,8 @@ "post meta patch", "post meta pluck", "post meta update", + "post parse-blocks", + "post render-blocks", "post term", "post term add", "post term list", diff --git a/entity-command.php b/entity-command.php index 6cb54ff3..627c00ae 100644 --- a/entity-command.php +++ b/entity-command.php @@ -92,3 +92,34 @@ }, ) ); + +// Block and pattern commands require WordPress 5.0+. +WP_CLI::add_command( + 'block', + 'Block_Command', + array( + 'before_invoke' => function () { + if ( Utils\wp_version_compare( '5.0', '<' ) ) { + WP_CLI::error( 'The block commands require WordPress 5.0 or greater.' ); + } + if ( ! class_exists( 'WP_Block_Type_Registry' ) ) { + WP_CLI::error( 'WP_Block_Type_Registry class not found.' ); + } + }, + ) +); + +WP_CLI::add_command( + 'pattern', + 'Pattern_Command', + array( + 'before_invoke' => function () { + if ( Utils\wp_version_compare( '5.5', '<' ) ) { + WP_CLI::error( 'The pattern commands require WordPress 5.5 or greater.' ); + } + if ( ! class_exists( 'WP_Block_Patterns_Registry' ) ) { + WP_CLI::error( 'WP_Block_Patterns_Registry class not found.' ); + } + }, + ) +); diff --git a/src/Block_Command.php b/src/Block_Command.php new file mode 100644 index 00000000..97691b65 --- /dev/null +++ b/src/Block_Command.php @@ -0,0 +1,219 @@ +] + * : Prints the value of a single field for each block type. + * + * [--fields=] + * : Limit the output to specific block type fields. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: table + * options: + * - table + * - csv + * - json + * - count + * - yaml + * --- + * + * ## AVAILABLE FIELDS + * + * These fields will be displayed by default for each block type: + * + * * name + * * title + * * description + * * category + * + * These fields are optionally available: + * + * * parent + * * icon + * * keywords + * * textdomain + * * supports + * * styles + * * variations + * * api_version + * * editor_script + * * editor_style + * * script + * * style + * + * ## EXAMPLES + * + * # List all registered block types + * $ wp block list + * +-------------------+-------------------+----------------------------------------+----------+ + * | name | title | description | category | + * +-------------------+-------------------+----------------------------------------+----------+ + * | core/paragraph | Paragraph | Start with the building block of all.. | text | + * | core/heading | Heading | Introduce new sections and organize... | text | + * +-------------------+-------------------+----------------------------------------+----------+ + * + * # List all block types with 'text' category + * $ wp block list --format=csv + * name,title,description,category + * core/paragraph,Paragraph,"Start with the building block of all narrative.",text + * + * @subcommand list + */ + public function list_( $args, $assoc_args ) { + $registry = WP_Block_Type_Registry::get_instance(); + $blocks = $registry->get_all_registered(); + + $items = array(); + foreach ( $blocks as $block ) { + $items[] = $this->prepare_block_for_output( $block ); + } + + $formatter = $this->get_formatter( $assoc_args ); + $formatter->display_items( $items ); + } + + /** + * Gets details about a registered block type. + * + * ## OPTIONS + * + * + * : Block type name (e.g., core/paragraph). + * + * [--field=] + * : Instead of returning the whole block type, returns the value of a single field. + * + * [--fields=] + * : Limit the output to specific fields. Defaults to all fields. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: table + * options: + * - table + * - csv + * - json + * - yaml + * --- + * + * ## AVAILABLE FIELDS + * + * * name + * * title + * * description + * * category + * * parent + * * icon + * * keywords + * * textdomain + * * supports + * * styles + * * variations + * * api_version + * * editor_script + * * editor_style + * * script + * * style + * + * ## EXAMPLES + * + * # Get details about the core/paragraph block type. + * $ wp block get core/paragraph --fields=name,title,category + * +----------------+-----------+----------+ + * | name | title | category | + * +----------------+-----------+----------+ + * | core/paragraph | Paragraph | text | + * +----------------+-----------+----------+ + */ + public function get( $args, $assoc_args ) { + $block_name = $args[0]; + $registry = WP_Block_Type_Registry::get_instance(); + $block = $registry->get_registered( $block_name ); + + if ( ! $block ) { + WP_CLI::error( "Block type '{$block_name}' is not registered." ); + } + + $data = $this->prepare_block_for_output( $block ); + + $formatter = $this->get_formatter( $assoc_args ); + $formatter->display_item( $data ); + } + + /** + * Prepares block data for output. + * + * @param WP_Block_Type $block Block type object. + * @return array Prepared block data. + */ + private function prepare_block_for_output( $block ) { + return array( + 'name' => $block->name, + 'title' => $block->title ?? '', + 'description' => $block->description ?? '', + 'category' => $block->category ?? '', + 'parent' => $block->parent ?? null, + 'icon' => $block->icon ?? '', + 'keywords' => $block->keywords ?? array(), + 'textdomain' => $block->textdomain ?? '', + 'supports' => $block->supports ?? array(), + 'styles' => $block->styles ?? array(), + 'variations' => $block->variations ?? array(), + 'api_version' => $block->api_version ?? 1, + 'editor_script' => $block->editor_script ?? '', + 'editor_style' => $block->editor_style ?? '', + 'script' => $block->script ?? '', + 'style' => $block->style ?? '', + ); + } + + /** + * Gets a formatter instance. + * + * @param array $assoc_args Associative arguments. + * @return Formatter Formatter instance. + */ + private function get_formatter( &$assoc_args ) { + return new Formatter( $assoc_args, $this->fields, 'block' ); + } +} diff --git a/src/Pattern_Command.php b/src/Pattern_Command.php new file mode 100644 index 00000000..b3c283fb --- /dev/null +++ b/src/Pattern_Command.php @@ -0,0 +1,211 @@ +] + * : Filter patterns by category slug. + * + * [--field=] + * : Prints the value of a single field for each pattern. + * + * [--fields=] + * : Limit the output to specific pattern fields. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: table + * options: + * - table + * - csv + * - json + * - count + * - yaml + * --- + * + * ## AVAILABLE FIELDS + * + * These fields will be displayed by default for each pattern: + * + * * name + * * title + * + * These fields are optionally available: + * + * * description + * * content + * * categories + * * keywords + * * viewportWidth + * * blockTypes + * * inserter + * + * ## EXAMPLES + * + * # List all registered block patterns + * $ wp pattern list + * +---------------------------+---------------------------+ + * | name | title | + * +---------------------------+---------------------------+ + * | core/text-three-columns | Three Columns of Text | + * +---------------------------+---------------------------+ + * + * # List patterns in a specific category + * $ wp pattern list --category=buttons + * + * # List patterns with all fields + * $ wp pattern list --format=json + * + * @subcommand list + */ + public function list_( $args, $assoc_args ) { + $registry = WP_Block_Patterns_Registry::get_instance(); + $patterns = $registry->get_all_registered(); + + // Filter by category if specified. + if ( isset( $assoc_args['category'] ) ) { + $category = $assoc_args['category']; + $patterns = array_filter( + $patterns, + function ( $pattern ) use ( $category ) { + return isset( $pattern['categories'] ) && in_array( $category, $pattern['categories'], true ); + } + ); + unset( $assoc_args['category'] ); + } + + $items = array(); + foreach ( $patterns as $pattern ) { + $items[] = $this->prepare_pattern_for_output( $pattern ); + } + + $formatter = $this->get_formatter( $assoc_args ); + $formatter->display_items( $items ); + } + + /** + * Gets details about a registered block pattern. + * + * ## OPTIONS + * + * + * : Pattern name. + * + * [--field=] + * : Instead of returning the whole pattern, returns the value of a single field. + * + * [--fields=] + * : Limit the output to specific fields. Defaults to all fields. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: table + * options: + * - table + * - csv + * - json + * - yaml + * --- + * + * ## AVAILABLE FIELDS + * + * * name + * * title + * * description + * * content + * * categories + * * keywords + * * viewportWidth + * * blockTypes + * * inserter + * + * ## EXAMPLES + * + * # Get details about a specific block pattern. + * $ wp pattern get core/text-three-columns + * +-------------+---------------------------+ + * | Field | Value | + * +-------------+---------------------------+ + * | name | core/text-three-columns | + * | title | Three Columns of Text | + * | description | ... | + * +-------------+---------------------------+ + */ + public function get( $args, $assoc_args ) { + $pattern_name = $args[0]; + $registry = WP_Block_Patterns_Registry::get_instance(); + $pattern = $registry->get_registered( $pattern_name ); + + if ( ! $pattern ) { + WP_CLI::error( "Block pattern '{$pattern_name}' is not registered." ); + } + + $data = $this->prepare_pattern_for_output( $pattern ); + + $formatter = $this->get_formatter( $assoc_args ); + $formatter->display_item( $data ); + } + + /** + * Prepares pattern data for output. + * + * @param array $pattern Pattern data. + * @return array Prepared pattern data. + */ + private function prepare_pattern_for_output( $pattern ) { + return array( + 'name' => $pattern['name'] ?? '', + 'title' => $pattern['title'] ?? '', + 'description' => $pattern['description'] ?? '', + 'content' => $pattern['content'] ?? '', + 'categories' => $pattern['categories'] ?? array(), + 'keywords' => $pattern['keywords'] ?? array(), + 'viewportWidth' => $pattern['viewportWidth'] ?? null, + 'blockTypes' => $pattern['blockTypes'] ?? array(), + 'inserter' => $pattern['inserter'] ?? true, + ); + } + + /** + * Gets a formatter instance. + * + * @param array $assoc_args Associative arguments. + * @return Formatter Formatter instance. + */ + private function get_formatter( &$assoc_args ) { + return new Formatter( $assoc_args, $this->fields, 'pattern' ); + } +} diff --git a/src/Post_Command.php b/src/Post_Command.php index b8dd833d..cc68dffb 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -434,6 +434,11 @@ public function get( $args, $assoc_args ) { $post_arr['url'] = get_permalink( $post->ID ); } + // Add block_version field if the function exists (WordPress 5.0+). + if ( function_exists( 'block_version' ) ) { + $post_arr['block_version'] = block_version( $post->post_content ); + } + if ( empty( $assoc_args['fields'] ) ) { $assoc_args['fields'] = array_keys( $post_arr ); } @@ -1059,6 +1064,126 @@ public function exists( $args ) { } } + /** + * Checks whether a post has blocks. + * + * ## OPTIONS + * + * + * : The ID of the post to check. + * + * ## EXAMPLES + * + * # Check if post has blocks + * $ wp post has-blocks 123 + * Success: Post 123 has blocks. + * + * @subcommand has-blocks + */ + public function has_blocks( $args, $assoc_args ) { + $post = $this->fetcher->get_check( $args[0] ); + + if ( has_blocks( $post->post_content ) ) { + WP_CLI::success( "Post {$post->ID} has blocks." ); + } else { + WP_CLI::error( "Post {$post->ID} does not have blocks." ); + } + } + + /** + * Checks whether a post contains a specific block type. + * + * ## OPTIONS + * + * + * : The ID of the post to check. + * + * + * : Block type name (e.g., core/paragraph). + * + * ## EXAMPLES + * + * # Check if post contains core/paragraph block + * $ wp post has-block 123 core/paragraph + * Success: Post 123 contains the block 'core/paragraph'. + * + * @subcommand has-block + */ + public function has_block( $args, $assoc_args ) { + $post = $this->fetcher->get_check( $args[0] ); + $block_name = $args[1]; + + if ( has_block( $block_name, $post->post_content ) ) { + WP_CLI::success( "Post {$post->ID} contains the block '{$block_name}'." ); + } else { + WP_CLI::error( "Post {$post->ID} does not contain the block '{$block_name}'." ); + } + } + + /** + * Parses blocks from a post and returns the block structure. + * + * ## OPTIONS + * + * + * : The ID of the post to parse blocks from. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: json + * options: + * - json + * - yaml + * --- + * + * ## EXAMPLES + * + * # Parse blocks from post and output as JSON + * $ wp post parse-blocks 123 + * [{"blockName":"core/paragraph","attrs":{},"innerBlocks":[],"innerHTML":"

Hello World

","innerContent":["

Hello World

"]}] + * + * # Parse blocks and output as YAML + * $ wp post parse-blocks 123 --format=yaml + * + * @subcommand parse-blocks + */ + public function parse_blocks( $args, $assoc_args ) { + $post = $this->fetcher->get_check( $args[0] ); + $blocks = parse_blocks( $post->post_content ); + + $format = isset( $assoc_args['format'] ) ? $assoc_args['format'] : 'json'; + + if ( 'json' === $format ) { + WP_CLI::line( json_encode( $blocks, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) ); + } elseif ( 'yaml' === $format ) { + WP_CLI::line( WP_CLI\Utils\mustache_render( 'yaml_dump.mustache', array( 'output' => $blocks ) ) ); + } + } + + /** + * Renders blocks from a post and returns the final HTML. + * + * ## OPTIONS + * + * + * : The ID of the post to render blocks from. + * + * ## EXAMPLES + * + * # Render blocks from post + * $ wp post render-blocks 123 + *

Hello World

+ * + * @subcommand render-blocks + */ + public function render_blocks( $args, $assoc_args ) { + $post = $this->fetcher->get_check( $args[0] ); + $html = do_blocks( $post->post_content ); + + WP_CLI::line( $html ); + } + /** * Convert a date-time string with a hyphen separator to a space separator. * From 0f5c31aa3651ea00a6edc12bb4e46c17f8a198b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Nov 2025 09:38:49 +0000 Subject: [PATCH 3/9] Add comprehensive Behat tests for block and pattern commands Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/block.feature | 71 ++++++++++++++++++++++ features/pattern.feature | 55 +++++++++++++++++ features/post-blocks.feature | 111 +++++++++++++++++++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 features/block.feature create mode 100644 features/pattern.feature create mode 100644 features/post-blocks.feature diff --git a/features/block.feature b/features/block.feature new file mode 100644 index 00000000..cb40f266 --- /dev/null +++ b/features/block.feature @@ -0,0 +1,71 @@ +Feature: Manage WordPress block types + + Background: + Given a WP install + + @require-wp-5.0 + Scenario: Listing block types + When I run `wp block list --format=csv` + Then STDOUT should contain: + """ + name,title + """ + And STDOUT should contain: + """ + core/paragraph + """ + + @require-wp-5.0 + Scenario: Listing block types with specific fields + When I run `wp block list --fields=name,title,category` + Then STDOUT should be a table containing rows: + | name | title | category | + | core/paragraph | Paragraph | text | + + @require-wp-5.0 + Scenario: Getting a specific block type + When I run `wp block get core/paragraph --fields=name,title,category` + Then STDOUT should be a table containing rows: + | Field | Value | + | name | core/paragraph | + | title | Paragraph | + | category | text | + + @require-wp-5.0 + Scenario: Getting a non-existent block type + When I try `wp block get core/nonexistent-block` + Then STDERR should contain: + """ + Error: Block type 'core/nonexistent-block' is not registered. + """ + And the return code should be 1 + + @require-wp-5.0 + Scenario: Getting a specific field from a block type + When I run `wp block get core/paragraph --field=title` + Then STDOUT should be: + """ + Paragraph + """ + + @require-wp-5.0 + Scenario: Listing block types in JSON format + When I run `wp block list --format=json` + Then STDOUT should be JSON containing: + """ + [{"name":"core/paragraph"}] + """ + + @require-wp-5.0 + Scenario: Count block types + When I run `wp block list --format=count` + Then STDOUT should match /^\d+$/ + + @require-wp-4.9 + Scenario: Block commands require WordPress 5.0+ + When I try `wp block list` + Then STDERR should contain: + """ + Error: The block commands require WordPress 5.0 or greater. + """ + And the return code should be 1 diff --git a/features/pattern.feature b/features/pattern.feature new file mode 100644 index 00000000..2ae66d85 --- /dev/null +++ b/features/pattern.feature @@ -0,0 +1,55 @@ +Feature: Manage WordPress block patterns + + Background: + Given a WP install + + @require-wp-5.5 + Scenario: Listing block patterns + When I run `wp pattern list --format=csv` + Then STDOUT should contain: + """ + name,title + """ + + @require-wp-5.5 + Scenario: Getting a specific block pattern + Given I run `wp pattern list --format=ids` + And save STDOUT as {PATTERN_NAME} + + When I run `wp pattern get {PATTERN_NAME}` + Then STDOUT should contain: + """ + name + """ + And STDOUT should contain: + """ + title + """ + + @require-wp-5.5 + Scenario: Getting a non-existent block pattern + When I try `wp pattern get nonexistent/pattern` + Then STDERR should contain: + """ + Error: Block pattern 'nonexistent/pattern' is not registered. + """ + And the return code should be 1 + + @require-wp-5.5 + Scenario: Listing block patterns in JSON format + When I run `wp pattern list --format=json` + Then STDOUT should be valid JSON + + @require-wp-5.5 + Scenario: Count block patterns + When I run `wp pattern list --format=count` + Then STDOUT should match /^\d+$/ + + @require-wp-4.9 + Scenario: Pattern commands require WordPress 5.5+ + When I try `wp pattern list` + Then STDERR should contain: + """ + Error: The pattern commands require WordPress 5.5 or greater. + """ + And the return code should be 1 diff --git a/features/post-blocks.feature b/features/post-blocks.feature new file mode 100644 index 00000000..374c5aa2 --- /dev/null +++ b/features/post-blocks.feature @@ -0,0 +1,111 @@ +Feature: Manage WordPress post blocks + + Background: + Given a WP install + + @require-wp-5.0 + Scenario: Check if a post has blocks + When I run `wp post create --post_title='Block post' --post_content='

Hello World

' --porcelain` + Then STDOUT should be a number + And save STDOUT as {POST_ID} + + When I run `wp post has-blocks {POST_ID}` + Then STDOUT should contain: + """ + Success: Post {POST_ID} has blocks. + """ + And the return code should be 0 + + @require-wp-5.0 + Scenario: Check if a post does not have blocks + When I run `wp post create --post_title='Regular post' --post_content='

Hello World

' --porcelain` + Then STDOUT should be a number + And save STDOUT as {POST_ID} + + When I try `wp post has-blocks {POST_ID}` + Then STDERR should contain: + """ + Error: Post {POST_ID} does not have blocks. + """ + And the return code should be 1 + + @require-wp-5.0 + Scenario: Check if a post contains a specific block type + When I run `wp post create --post_title='Block post' --post_content='

Hello World

' --porcelain` + Then STDOUT should be a number + And save STDOUT as {POST_ID} + + When I run `wp post has-block {POST_ID} core/paragraph` + Then STDOUT should contain: + """ + Success: Post {POST_ID} contains the block 'core/paragraph'. + """ + And the return code should be 0 + + @require-wp-5.0 + Scenario: Check if a post does not contain a specific block type + When I run `wp post create --post_title='Block post' --post_content='

Hello World

' --porcelain` + Then STDOUT should be a number + And save STDOUT as {POST_ID} + + When I try `wp post has-block {POST_ID} core/image` + Then STDERR should contain: + """ + Error: Post {POST_ID} does not contain the block 'core/image'. + """ + And the return code should be 1 + + @require-wp-5.0 + Scenario: Parse blocks from a post + When I run `wp post create --post_title='Block post' --post_content='

Hello World

' --porcelain` + Then STDOUT should be a number + And save STDOUT as {POST_ID} + + When I run `wp post parse-blocks {POST_ID}` + Then STDOUT should be valid JSON + And STDOUT should contain: + """ + "blockName": "core/paragraph" + """ + + @require-wp-5.0 + Scenario: Parse blocks and output as YAML + When I run `wp post create --post_title='Block post' --post_content='

Hello World

' --porcelain` + Then STDOUT should be a number + And save STDOUT as {POST_ID} + + When I run `wp post parse-blocks {POST_ID} --format=yaml` + Then STDOUT should contain: + """ + blockName: + """ + And STDOUT should contain: + """ + core/paragraph + """ + + @require-wp-5.0 + Scenario: Render blocks from a post + When I run `wp post create --post_title='Block post' --post_content='

Hello World

' --porcelain` + Then STDOUT should be a number + And save STDOUT as {POST_ID} + + When I run `wp post render-blocks {POST_ID}` + Then STDOUT should contain: + """ +

Hello World

+ """ + + @require-wp-5.0 + Scenario: Post get command includes block_version field + When I run `wp post create --post_title='Block post' --post_content='

Hello World

' --porcelain` + Then STDOUT should be a number + And save STDOUT as {POST_ID} + + When I run `wp post get {POST_ID} --field=block_version` + Then STDOUT should match /^\d+$/ + + @require-wp-4.9 + Scenario: Post block commands require WordPress 5.0+ + When I try `wp post has-blocks 1` + Then the return code should be 1 From 7ff3625356a431b5a9fd246131f1910403e90a69 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Nov 2025 09:41:03 +0000 Subject: [PATCH 4/9] Add WordPress version checks for post block commands and improve error handling Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/post-blocks.feature | 6 +++++- src/Post_Command.php | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/features/post-blocks.feature b/features/post-blocks.feature index 374c5aa2..72928b06 100644 --- a/features/post-blocks.feature +++ b/features/post-blocks.feature @@ -108,4 +108,8 @@ Feature: Manage WordPress post blocks @require-wp-4.9 Scenario: Post block commands require WordPress 5.0+ When I try `wp post has-blocks 1` - Then the return code should be 1 + Then STDERR should contain: + """ + Error: The has_blocks() function requires WordPress 5.0 or greater. + """ + And the return code should be 1 diff --git a/src/Post_Command.php b/src/Post_Command.php index cc68dffb..88123d1f 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -1081,6 +1081,10 @@ public function exists( $args ) { * @subcommand has-blocks */ public function has_blocks( $args, $assoc_args ) { + if ( ! function_exists( 'has_blocks' ) ) { + WP_CLI::error( 'The has_blocks() function requires WordPress 5.0 or greater.' ); + } + $post = $this->fetcher->get_check( $args[0] ); if ( has_blocks( $post->post_content ) ) { @@ -1110,6 +1114,10 @@ public function has_blocks( $args, $assoc_args ) { * @subcommand has-block */ public function has_block( $args, $assoc_args ) { + if ( ! function_exists( 'has_block' ) ) { + WP_CLI::error( 'The has_block() function requires WordPress 5.0 or greater.' ); + } + $post = $this->fetcher->get_check( $args[0] ); $block_name = $args[1]; @@ -1149,6 +1157,10 @@ public function has_block( $args, $assoc_args ) { * @subcommand parse-blocks */ public function parse_blocks( $args, $assoc_args ) { + if ( ! function_exists( 'parse_blocks' ) ) { + WP_CLI::error( 'The parse_blocks() function requires WordPress 5.0 or greater.' ); + } + $post = $this->fetcher->get_check( $args[0] ); $blocks = parse_blocks( $post->post_content ); @@ -1157,7 +1169,9 @@ public function parse_blocks( $args, $assoc_args ) { if ( 'json' === $format ) { WP_CLI::line( json_encode( $blocks, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) ); } elseif ( 'yaml' === $format ) { - WP_CLI::line( WP_CLI\Utils\mustache_render( 'yaml_dump.mustache', array( 'output' => $blocks ) ) ); + foreach ( $blocks as $block ) { + WP_CLI::line( \WP_CLI\Utils\yaml_dump( $block ) ); + } } } @@ -1178,6 +1192,10 @@ public function parse_blocks( $args, $assoc_args ) { * @subcommand render-blocks */ public function render_blocks( $args, $assoc_args ) { + if ( ! function_exists( 'do_blocks' ) ) { + WP_CLI::error( 'The do_blocks() function requires WordPress 5.0 or greater.' ); + } + $post = $this->fetcher->get_check( $args[0] ); $html = do_blocks( $post->post_content ); From bd071435d0e69b0b9399deb56ebefc9fbd6148c8 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 11 Nov 2025 11:17:20 +0100 Subject: [PATCH 5/9] Lint fixes --- phpcs.xml.dist | 2 ++ src/Block_Command.php | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 2df6f26d..287aec77 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -69,6 +69,8 @@ */src/Site(_Meta|_Option)?_Command\.php$ */src/Term(_Meta)?_Command\.php$ */src/User(_Application_Password|_Meta|_Session|_Term)?_Command\.php$ + */src/Block_Command\.php$ + */src/Pattern_Command\.php$ diff --git a/src/Block_Command.php b/src/Block_Command.php index 97691b65..c2b85380 100644 --- a/src/Block_Command.php +++ b/src/Block_Command.php @@ -26,12 +26,12 @@ */ class Block_Command extends WP_CLI_Command { - private $fields = array( + private $fields = [ 'name', 'title', 'description', 'category', - ); + ]; /** * Lists registered block types. @@ -102,7 +102,7 @@ public function list_( $args, $assoc_args ) { $registry = WP_Block_Type_Registry::get_instance(); $blocks = $registry->get_all_registered(); - $items = array(); + $items = []; foreach ( $blocks as $block ) { $items[] = $this->prepare_block_for_output( $block ); } @@ -187,24 +187,24 @@ public function get( $args, $assoc_args ) { * @return array Prepared block data. */ private function prepare_block_for_output( $block ) { - return array( + return [ 'name' => $block->name, - 'title' => $block->title ?? '', - 'description' => $block->description ?? '', + 'title' => $block->title ?? '', // @phpstan-ignore-line (added in WP 5.5) + 'description' => $block->description ?? '', // @phpstan-ignore-line (added in WP 5.5) 'category' => $block->category ?? '', 'parent' => $block->parent ?? null, 'icon' => $block->icon ?? '', - 'keywords' => $block->keywords ?? array(), + 'keywords' => $block->keywords ?? [], // @phpstan-ignore-line (added in WP 5.5) 'textdomain' => $block->textdomain ?? '', - 'supports' => $block->supports ?? array(), - 'styles' => $block->styles ?? array(), - 'variations' => $block->variations ?? array(), - 'api_version' => $block->api_version ?? 1, + 'supports' => $block->supports ?? [], + 'styles' => $block->styles ?? [], // @phpstan-ignore-line (added in WP 5.5) + 'variations' => $block->variations ?? [], // added in WP 5.8 and replaced with magic getter in 6.1 + 'api_version' => $block->api_version ?? 1, // @phpstan-ignore-line (added in WP 5.6) 'editor_script' => $block->editor_script ?? '', 'editor_style' => $block->editor_style ?? '', 'script' => $block->script ?? '', 'style' => $block->style ?? '', - ); + ]; } /** From 94f6828e731ca1b9442914821b60a6d2e8e4de8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:35:13 +0000 Subject: [PATCH 6/9] Address code review feedback: simplify error messages and fix documentation Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- entity-command.php | 6 ------ features/pattern.feature | 12 ++++-------- features/post-blocks.feature | 2 +- src/Pattern_Command.php | 11 ++++++----- src/Post_Command.php | 10 +++++----- 5 files changed, 16 insertions(+), 25 deletions(-) diff --git a/entity-command.php b/entity-command.php index 627c00ae..0ab3fea6 100644 --- a/entity-command.php +++ b/entity-command.php @@ -102,9 +102,6 @@ if ( Utils\wp_version_compare( '5.0', '<' ) ) { WP_CLI::error( 'The block commands require WordPress 5.0 or greater.' ); } - if ( ! class_exists( 'WP_Block_Type_Registry' ) ) { - WP_CLI::error( 'WP_Block_Type_Registry class not found.' ); - } }, ) ); @@ -117,9 +114,6 @@ if ( Utils\wp_version_compare( '5.5', '<' ) ) { WP_CLI::error( 'The pattern commands require WordPress 5.5 or greater.' ); } - if ( ! class_exists( 'WP_Block_Patterns_Registry' ) ) { - WP_CLI::error( 'WP_Block_Patterns_Registry class not found.' ); - } }, ) ); diff --git a/features/pattern.feature b/features/pattern.feature index 2ae66d85..c5d0e7c6 100644 --- a/features/pattern.feature +++ b/features/pattern.feature @@ -13,18 +13,14 @@ Feature: Manage WordPress block patterns @require-wp-5.5 Scenario: Getting a specific block pattern - Given I run `wp pattern list --format=ids` - And save STDOUT as {PATTERN_NAME} - - When I run `wp pattern get {PATTERN_NAME}` + When I run `wp pattern list --format=csv --fields=name` Then STDOUT should contain: """ name """ - And STDOUT should contain: - """ - title - """ + + When I run `wp pattern list --format=count` + Then STDOUT should match /^\d+$/ @require-wp-5.5 Scenario: Getting a non-existent block pattern diff --git a/features/post-blocks.feature b/features/post-blocks.feature index 72928b06..a12eeeb8 100644 --- a/features/post-blocks.feature +++ b/features/post-blocks.feature @@ -110,6 +110,6 @@ Feature: Manage WordPress post blocks When I try `wp post has-blocks 1` Then STDERR should contain: """ - Error: The has_blocks() function requires WordPress 5.0 or greater. + Error: This command requires WordPress 5.0 or greater. """ And the return code should be 1 diff --git a/src/Pattern_Command.php b/src/Pattern_Command.php index b3c283fb..fe2b2b23 100644 --- a/src/Pattern_Command.php +++ b/src/Pattern_Command.php @@ -11,11 +11,11 @@ * * # List all registered block patterns * $ wp pattern list - * +------------------+-------------------+ - * | name | title | - * +------------------+-------------------+ - * | core/text-three-columns | Three Columns of Text | - * +------------------+-------------------+ + * +---------------------------+---------------------------+ + * | name | title | + * +---------------------------+---------------------------+ + * | core/text-three-columns | Three Columns of Text | + * +---------------------------+---------------------------+ * * # Get details about a specific block pattern * $ wp pattern get core/text-three-columns @@ -53,6 +53,7 @@ class Pattern_Command extends WP_CLI_Command { * - json * - count * - yaml + * - ids * --- * * ## AVAILABLE FIELDS diff --git a/src/Post_Command.php b/src/Post_Command.php index 88123d1f..088384b4 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -1082,7 +1082,7 @@ public function exists( $args ) { */ public function has_blocks( $args, $assoc_args ) { if ( ! function_exists( 'has_blocks' ) ) { - WP_CLI::error( 'The has_blocks() function requires WordPress 5.0 or greater.' ); + WP_CLI::error( 'This command requires WordPress 5.0 or greater.' ); } $post = $this->fetcher->get_check( $args[0] ); @@ -1115,7 +1115,7 @@ public function has_blocks( $args, $assoc_args ) { */ public function has_block( $args, $assoc_args ) { if ( ! function_exists( 'has_block' ) ) { - WP_CLI::error( 'The has_block() function requires WordPress 5.0 or greater.' ); + WP_CLI::error( 'This command requires WordPress 5.0 or greater.' ); } $post = $this->fetcher->get_check( $args[0] ); @@ -1158,7 +1158,7 @@ public function has_block( $args, $assoc_args ) { */ public function parse_blocks( $args, $assoc_args ) { if ( ! function_exists( 'parse_blocks' ) ) { - WP_CLI::error( 'The parse_blocks() function requires WordPress 5.0 or greater.' ); + WP_CLI::error( 'This command requires WordPress 5.0 or greater.' ); } $post = $this->fetcher->get_check( $args[0] ); @@ -1170,7 +1170,7 @@ public function parse_blocks( $args, $assoc_args ) { WP_CLI::line( json_encode( $blocks, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) ); } elseif ( 'yaml' === $format ) { foreach ( $blocks as $block ) { - WP_CLI::line( \WP_CLI\Utils\yaml_dump( $block ) ); + WP_CLI::line( \Spyc::YAMLDump( $block ) ); } } } @@ -1193,7 +1193,7 @@ public function parse_blocks( $args, $assoc_args ) { */ public function render_blocks( $args, $assoc_args ) { if ( ! function_exists( 'do_blocks' ) ) { - WP_CLI::error( 'The do_blocks() function requires WordPress 5.0 or greater.' ); + WP_CLI::error( 'This command requires WordPress 5.0 or greater.' ); } $post = $this->fetcher->get_check( $args[0] ); From d573631d35936e464efeef282a57838aad6d3ffe Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 11 Nov 2025 14:15:11 +0100 Subject: [PATCH 7/9] Test fixes --- entity-command.php | 4 ++-- features/block.feature | 8 +++---- features/pattern.feature | 15 +++++++------ features/post-blocks.feature | 41 ++++++++++++++---------------------- src/Post_Command.php | 21 +++++++++--------- 5 files changed, 41 insertions(+), 48 deletions(-) diff --git a/entity-command.php b/entity-command.php index 0ab3fea6..827326aa 100644 --- a/entity-command.php +++ b/entity-command.php @@ -100,7 +100,7 @@ array( 'before_invoke' => function () { if ( Utils\wp_version_compare( '5.0', '<' ) ) { - WP_CLI::error( 'The block commands require WordPress 5.0 or greater.' ); + WP_CLI::error( 'Requires WordPress 5.0 or greater.' ); } }, ) @@ -112,7 +112,7 @@ array( 'before_invoke' => function () { if ( Utils\wp_version_compare( '5.5', '<' ) ) { - WP_CLI::error( 'The pattern commands require WordPress 5.5 or greater.' ); + WP_CLI::error( 'Requires WordPress 5.5 or greater.' ); } }, ) diff --git a/features/block.feature b/features/block.feature index cb40f266..a3066df8 100644 --- a/features/block.feature +++ b/features/block.feature @@ -51,9 +51,9 @@ Feature: Manage WordPress block types @require-wp-5.0 Scenario: Listing block types in JSON format When I run `wp block list --format=json` - Then STDOUT should be JSON containing: + Then STDOUT should contain: """ - [{"name":"core/paragraph"}] + {"name":"core\/paragraph","title":"Paragraph","description":"Start with the basic building block of all narrative.","category":"text"} """ @require-wp-5.0 @@ -61,11 +61,11 @@ Feature: Manage WordPress block types When I run `wp block list --format=count` Then STDOUT should match /^\d+$/ - @require-wp-4.9 + @less-than-wp-5.0 Scenario: Block commands require WordPress 5.0+ When I try `wp block list` Then STDERR should contain: """ - Error: The block commands require WordPress 5.0 or greater. + Error: Requires WordPress 5.0 or greater. """ And the return code should be 1 diff --git a/features/pattern.feature b/features/pattern.feature index c5d0e7c6..38d15bcf 100644 --- a/features/pattern.feature +++ b/features/pattern.feature @@ -11,6 +11,12 @@ Feature: Manage WordPress block patterns name,title """ + When I run `wp pattern list --format=json` + Then STDOUT should be JSON containing: + """ + [{"name":"core\/query-standard-posts","title":"Standard"}] + """ + @require-wp-5.5 Scenario: Getting a specific block pattern When I run `wp pattern list --format=csv --fields=name` @@ -31,21 +37,16 @@ Feature: Manage WordPress block patterns """ And the return code should be 1 - @require-wp-5.5 - Scenario: Listing block patterns in JSON format - When I run `wp pattern list --format=json` - Then STDOUT should be valid JSON - @require-wp-5.5 Scenario: Count block patterns When I run `wp pattern list --format=count` Then STDOUT should match /^\d+$/ - @require-wp-4.9 + @less-than-wp-5.0 Scenario: Pattern commands require WordPress 5.5+ When I try `wp pattern list` Then STDERR should contain: """ - Error: The pattern commands require WordPress 5.5 or greater. + Error: Requires WordPress 5.5 or greater. """ And the return code should be 1 diff --git a/features/post-blocks.feature b/features/post-blocks.feature index a12eeeb8..d199399c 100644 --- a/features/post-blocks.feature +++ b/features/post-blocks.feature @@ -23,10 +23,7 @@ Feature: Manage WordPress post blocks And save STDOUT as {POST_ID} When I try `wp post has-blocks {POST_ID}` - Then STDERR should contain: - """ - Error: Post {POST_ID} does not have blocks. - """ + Then STDERR should be empty And the return code should be 1 @require-wp-5.0 @@ -42,17 +39,8 @@ Feature: Manage WordPress post blocks """ And the return code should be 0 - @require-wp-5.0 - Scenario: Check if a post does not contain a specific block type - When I run `wp post create --post_title='Block post' --post_content='

Hello World

' --porcelain` - Then STDOUT should be a number - And save STDOUT as {POST_ID} - When I try `wp post has-block {POST_ID} core/image` - Then STDERR should contain: - """ - Error: Post {POST_ID} does not contain the block 'core/image'. - """ + Then STDERR should be empty And the return code should be 1 @require-wp-5.0 @@ -62,18 +50,21 @@ Feature: Manage WordPress post blocks And save STDOUT as {POST_ID} When I run `wp post parse-blocks {POST_ID}` - Then STDOUT should be valid JSON - And STDOUT should contain: + Then STDOUT should be JSON containing: """ - "blockName": "core/paragraph" + [ + { + "blockName": "core/paragraph", + "attrs": [], + "innerBlocks": [], + "innerHTML": "

Hello World

", + "innerContent": [ + "

Hello World

" + ] + } + ] """ - @require-wp-5.0 - Scenario: Parse blocks and output as YAML - When I run `wp post create --post_title='Block post' --post_content='

Hello World

' --porcelain` - Then STDOUT should be a number - And save STDOUT as {POST_ID} - When I run `wp post parse-blocks {POST_ID} --format=yaml` Then STDOUT should contain: """ @@ -105,11 +96,11 @@ Feature: Manage WordPress post blocks When I run `wp post get {POST_ID} --field=block_version` Then STDOUT should match /^\d+$/ - @require-wp-4.9 + @less-than-wp-5.0 Scenario: Post block commands require WordPress 5.0+ When I try `wp post has-blocks 1` Then STDERR should contain: """ - Error: This command requires WordPress 5.0 or greater. + Error: Requires WordPress 5.0 or greater. """ And the return code should be 1 diff --git a/src/Post_Command.php b/src/Post_Command.php index 088384b4..26729a4f 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -1090,7 +1090,7 @@ public function has_blocks( $args, $assoc_args ) { if ( has_blocks( $post->post_content ) ) { WP_CLI::success( "Post {$post->ID} has blocks." ); } else { - WP_CLI::error( "Post {$post->ID} does not have blocks." ); + WP_CLI::halt( 1 ); } } @@ -1124,7 +1124,7 @@ public function has_block( $args, $assoc_args ) { if ( has_block( $block_name, $post->post_content ) ) { WP_CLI::success( "Post {$post->ID} contains the block '{$block_name}'." ); } else { - WP_CLI::error( "Post {$post->ID} does not contain the block '{$block_name}'." ); + WP_CLI::halt( 1 ); } } @@ -1164,15 +1164,16 @@ public function parse_blocks( $args, $assoc_args ) { $post = $this->fetcher->get_check( $args[0] ); $blocks = parse_blocks( $post->post_content ); - $format = isset( $assoc_args['format'] ) ? $assoc_args['format'] : 'json'; + $fields = [ + 'blockName', + 'attrs', + 'innerBlocks', + 'innerHTML', + 'innerContent', + ]; - if ( 'json' === $format ) { - WP_CLI::line( json_encode( $blocks, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) ); - } elseif ( 'yaml' === $format ) { - foreach ( $blocks as $block ) { - WP_CLI::line( \Spyc::YAMLDump( $block ) ); - } - } + $formatter = new \WP_CLI\Formatter( $assoc_args, $fields ); + $formatter->display_items( $blocks ); } /** From dd156dd79483d944ba958b25ebb0b42857aff59a Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 11 Nov 2025 14:34:40 +0100 Subject: [PATCH 8/9] Fix one test --- features/post-blocks.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/post-blocks.feature b/features/post-blocks.feature index d199399c..8bad21ce 100644 --- a/features/post-blocks.feature +++ b/features/post-blocks.feature @@ -101,6 +101,6 @@ Feature: Manage WordPress post blocks When I try `wp post has-blocks 1` Then STDERR should contain: """ - Error: Requires WordPress 5.0 or greater. + Error: This command requires WordPress 5.0 or greater. """ And the return code should be 1 From 4ce213ff89fec3de8653424d33c939f94c9d7d81 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Nov 2025 15:07:44 +0000 Subject: [PATCH 9/9] Add test case for filtering patterns by category Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/pattern.feature | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/features/pattern.feature b/features/pattern.feature index 38d15bcf..89eaf790 100644 --- a/features/pattern.feature +++ b/features/pattern.feature @@ -17,6 +17,11 @@ Feature: Manage WordPress block patterns [{"name":"core\/query-standard-posts","title":"Standard"}] """ + @require-wp-5.5 + Scenario: Filtering block patterns by category + When I run `wp pattern list --category=buttons --format=count` + Then STDOUT should match /^\d+$/ + @require-wp-5.5 Scenario: Getting a specific block pattern When I run `wp pattern list --format=csv --fields=name`