Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,27 @@ export function validateModules(modules) {
// still getting security fixes, and matches the widest install base.
export const MIN_PHP = '8.2';

// readme.txt "Tested up to" seed — a generated scaffold can't know the WP
// release it'll actually be tested against, so this is a recent stable floor
// the developer bumps per release, never a claim of real testing. Bump this
// one line as WordPress releases; wp.org shows an "untested with your version"
// warning once a plugin's declared value trails current by much.
export const TESTED_UP_TO = '7.1';

// Dot-separated numeric version compare (WordPress/PHP-style "x.y" or
// "x.y.z" strings only — no pre-release suffixes). `parseFloat` on a version
// string mis-parses two-digit segments (parseFloat('6.10') === 6.1), so this
// compares each segment as its own integer instead.
export function versionGte(a, b) {
const as = a.split('.').map(Number);
const bs = b.split('.').map(Number);
for (let i = 0; i < Math.max(as.length, bs.length); i++) {
const diff = (as[i] || 0) - (bs[i] || 0);
if (diff !== 0) return diff > 0;
}
return true;
}

export function validateOutputDir(val) {
if (!val || typeof val !== 'string' || val.trim().length === 0) {
return 'Output directory is required.';
Expand Down Expand Up @@ -884,10 +905,10 @@ function scaffoldInto(answers, targetDir) {
'{{DESCRIPTION}}': answers.description,
'{{MIN_PHP}}': MIN_PHP,
'{{REQUIRES_AT_LEAST}}': requiredWpVersion,
// readme.txt "Tested up to". A generated scaffold can't know the WP
// release it'll be tested against, so seed a recent stable floor the
// developer bumps per release — never below what the plugin requires.
'{{TESTED_UP_TO}}': parseFloat(requiredWpVersion) > 6.9 ? requiredWpVersion : '6.9',
// readme.txt "Tested up to" — see TESTED_UP_TO. Never below what the
// plugin requires (a block-editor module can push requiredWpVersion
// past the seed).
'{{TESTED_UP_TO}}': versionGte(requiredWpVersion, TESTED_UP_TO) ? requiredWpVersion : TESTED_UP_TO,
'{{VERSION}}': '1.0.0',
'{{YEAR}}': new Date().getFullYear().toString(),
'{{PLUGIN_HEADER_EXTRA}}': pluginHeaderExtra,
Expand Down
4 changes: 2 additions & 2 deletions templates/src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
* Singleton bootloader for {{PLUGIN_NAME}}.
*
* Not a service container: modules are plain classes with an init_hooks()
* method, and shared services come from {@see Services}. One instance per
* request, reached with instance(); boot() is idempotent.
* method.{{#if has_services}} Shared services come from {@see Services}.{{/if}}
* One instance per request, reached with instance(); boot() is idempotent.
*/
final class Plugin {

Expand Down
37 changes: 37 additions & 0 deletions templates/src/Rest/Rest_Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public function register_routes() {
},
),
),
'schema' => $this->get_public_item_schema( ... ),
)
);
}
Expand Down Expand Up @@ -91,4 +92,40 @@ public function get_items( $request ) {

return rest_ensure_response( $data );
}

/**
* Item schema, describing the shape of get_items()'s response. Backs the
* endpoint's OPTIONS response and any schema-driven API tooling (the
* block editor's data layer included) -- without it the endpoint isn't
* self-describing.
*
* @return array
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}

$this->schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => '{{PREFIX}}_item',
'type' => 'object',
'properties' => array(
'message' => array(
'description' => __( 'Response message.', '{{SLUG}}' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'param' => array(
'description' => __( 'Echoed request parameter.', '{{SLUG}}' ),
'type' => array( 'string', 'null' ),
'context' => array( 'view' ),
'readonly' => true,
),
),
);

return $this->add_additional_fields_schema( $this->schema );
}
}
15 changes: 15 additions & 0 deletions templates/tests/Unit/Rest_Controller_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,19 @@ public function test_permissions_check_gates_on_capability(): void {

$this->assertFalse( $controller->get_items_permissions_check( $request ) );
}

/**
* The item schema describes get_items()'s response shape, so the
* endpoint is self-describing over OPTIONS rather than an empty default.
*/
public function test_get_item_schema_describes_the_response_shape(): void {
Functions\when( '__' )->returnArg();

$controller = new Rest_Controller();
$schema = $controller->get_item_schema();

$this->assertSame( 'object', $schema['type'] );
$this->assertArrayHasKey( 'message', $schema['properties'] );
$this->assertArrayHasKey( 'param', $schema['properties'] );
}
}
38 changes: 38 additions & 0 deletions templates/tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,44 @@ class WP_REST_Controller {
* @var string
*/
protected $rest_base;

/**
* The controller's cached item schema.
*
* @var array|null
*/
protected $schema;

/**
* Stub: real core strips arg-only properties before exposing a schema
* over OPTIONS; there are none to strip in this minimal stub.
*
* @return array
*/
public function get_public_item_schema() {
return $this->get_item_schema();
}

/**
* Stub: real core merges in schema for fields added via
* register_rest_field(); none are registered in this stub.
*
* @param array $schema Item schema.
* @return array
*/
protected function add_additional_fields_schema( $schema ) {
return $schema;
}

/**
* Stub default; a subclass implementing get_item_schema() overrides
* this.
*
* @return array
*/
public function get_item_schema() {
return array();
}
}
}

Expand Down
Loading