Skip to content
Open
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
71 changes: 71 additions & 0 deletions node-packages/wp-tooling/scaffolds/ci/test-measure/scaffold.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"slug": "test-measure",
"category": "ci",
"name": "CI: Test and Measure (consolidated)",
"description": "One caller workflow delegating lint (CSS/JS/PHP), JS tests, a PHP test matrix, build, the build-artifact gate, and optional a11y to the wp-ci.yml orchestrator in rtCamp/wp-shared-workflows. Jobs are gated on detected changes. Use this instead of the per-check ci/* callers; unlike them it supports a PHP x WP matrix and forwards private-repo tokens.",
"source": "template",
"inputs": [
{
"key": "wsw_ref",
"description": "Git ref of rtCamp/wp-shared-workflows to pin (tag, branch, or SHA).",
"default": "v1"
},
{
"key": "project_type",
"description": "Project shape the orchestrator presets on: plugin | theme | package.",
"default": "plugin"
},
{
"key": "default_branch",
"description": "Branch the push trigger watches (pull requests always trigger).",
"default": "main"
},
{
"key": "php_versions",
"description": "JSON array of PHP versions for the test-php matrix, crossed with wp_versions.",
"default": "[\"8.2\", \"8.3\", \"8.4\"]"
},
{
"key": "wp_versions",
"description": "JSON array of WordPress core versions for the test-php matrix, crossed with php_versions.",
"default": "[\"6.7\", \"6.8\", \"6.9\", \"7.0\"]"
},
{
"key": "test_php_exclude",
"description": "JSON array of {php, wp} cells to drop from the matrix, e.g. [{\"php\": \"8.4\", \"wp\": \"6.5\"}].",
"default": "[]"
},
{
"key": "run_a11y",
"description": "Set to true to run the (slow) pa11y accessibility job on every run.",
"default": "false"
}
],
"files": [
{
"src": "templates/test-measure.yml.mustache",
"dest": ".github/workflows/test-measure.yml"
}
],
"secrets": [
{
"key": "WP_TOOLING_TOKEN",
"scope": "github-actions",
"description": "Token with read access to the private rtCamp/wp-tooling repo (change detection installs the interim CLI). Needed only while that repo is private; one token may serve both secrets.",
"required": false
},
{
"key": "PACKAGES_TOKEN",
"scope": "github-actions",
"description": "Token with read access to private Composer source repos (e.g. rtCamp/wp-framework) for `composer install` in lint-php/test-php. Omit for projects with only public deps.",
"required": false
}
],
"tests": [
{
"src": "templates/test-measure.yml.mustache",
"dest": ".github/workflows/test-measure.yml",
"framework": "actionlint"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Test and Measure

# Scaffolded by @rtcamp/wp-tooling.
# Single caller that delegates lint, JS/PHP tests (PHP x WP matrix), build,
# the build-artifact gate, and optional a11y to the wp-ci.yml orchestrator;
# every job there is gated on detected changes.
# Source: https://github.com/rtCamp/wp-shared-workflows/blob/{{wsw_ref}}/.github/workflows/wp-ci.yml

on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
push:
branches:
- {{default_branch}}

permissions:
contents: read

# Cancel previous in-flight runs of this workflow for the same ref.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }}
cancel-in-progress: true

jobs:
ci:
uses: rtCamp/wp-shared-workflows/.github/workflows/wp-ci.yml@{{wsw_ref}}
with:
project-type: {{project_type}}
php-versions: '{{php_versions}}'
wp-versions: '{{wp_versions}}'
test-php-exclude: '{{test_php_exclude}}'{{#run_a11y}}
run-a11y: true{{/run_a11y}}
secrets:
wp-tooling-token: ${{ secrets.WP_TOOLING_TOKEN }}
packages-token: ${{ secrets.PACKAGES_TOKEN }}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ Write a test-case checklist covering:
- `wp/cron`: `wp_next_scheduled()`, callback fires, unschedule works.
- `wp/cli`: `WP_CLI::add_command` registered, `__invoke` behaviour, dry-run flag.

The engine's shipped test file already covers **Integration** for a plain instance of the kind (e.g. `post_type_exists()` for `wp/cpt` ships written and passing, not as a stub) — list it to confirm coverage, not to write it. Your effort in §7 goes to **Happy path / Edge cases / Error paths**: brief-specific behaviour the engine can't know.

Show the checklist to the developer. Ask: confirm, add, remove? Resolve before scaffolding. This is the cheapest place to catch a misread requirement.

### 5. Apply conventions, invoke the engine
Expand Down Expand Up @@ -127,7 +129,7 @@ Result shape: `{ scaffold, engine, developer, ai, warnings }`.
| `developer.install.composer` / `developer.install.npm` | Print as copy-paste command. **Never run `composer require` / `npm install`.** |
| `developer.secrets` | Print as `gh secret set` checklist. **Never read/write/log/transmit values.** |
| `ai.wiring` | Adaptive wiring with consent (see 6a). |
| `ai.tests` | Mandatory expansion under TDD loop (see 7). |
| `ai.tests` | Shipped complete + passing for the generic pattern; add brief-specific methods under the TDD loop (see 7). |
| `warnings` | Print to developer. |

#### 6a. Adaptive wiring
Expand All @@ -147,8 +149,8 @@ For block scaffolds, surface a developer action before testing: "run `npm run bu

| Step | Action |
|---|---|
| A | Expand the engine's stub into the full suite from §4's checklist. Strip every `markTestIncomplete`. |
| B | Run: `composer test` / `composer test:unit` (PHP), `npm run test:js` / `npx jest` (JS). Expect red. If the runner errors before running, invoke the relevant `setup/*` scaffold and retry. |
| A | Confirm the engine's shipped tests pass as-is (they cover §4's Integration row already, complete and green — not a step you perform). Write one new test method per remaining §4 row: the brief-specific behaviour the engine couldn't know. |
| B | Run: `composer test` / `composer test:unit` (PHP), `npm run test:js` / `npx jest` (JS). Expect red only for the methods just added — the shipped tests stay green throughout; one going red means you broke the generic pattern, so stop and investigate. Confirm each red is an assertion failure, not a bootstrap/fatal error (env or wiring trouble isn't a valid TDD red). Runner errors before running invoke the relevant `setup/*` scaffold and retry. |
| C | Implement just enough production code to flip **one** failing test green. |
| D | Re-run. Confirm that one test passes. |
| E | Loop B-D one test at a time. |
Expand Down Expand Up @@ -186,7 +188,7 @@ Escalation report format: **what you tried, what you observed, what's blocking,

## Hard rules - never violate

- Never write production code before its test exists on disk.
- Never hand-write behaviour code before its test exists on disk. (The engine's scaffolded class + test ship together, already passing, for the generic pattern — you didn't author it, so it's not a violation. The rule governs the brief-specific behaviour you add: test first, confirm red, then extend. Consented §6a wiring is likewise sanctioned, not authored behaviour.)
- Never hand-write an artifact the engine can scaffold.
- Never group multiple kinds under a per-feature folder (`Modules/<Feature>/...`).
- Never declare an artifact done without its test file passing.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,22 @@ class {{class}} extends AbstractAdminPage {
public function get_menu_title(): string {
return __( '{{menu_title}}', '{{text_domain}}' );
}

{{#parent_slug}}
/**
* Parent menu slug. Return null for a top-level menu.
* Parent menu slug (this page is a submenu under an existing menu).
*
* Emitted only when a parent slug is set. For a top-level menu the method is
* omitted so the framework's nullable default (get_parent_slug(): ?string
* returning null) applies: returning a concrete value from a ?string
* override would make the return type too wide for PHPStan level 5's
* checkTooWideReturnTypesInProtectedAndPublicMethods rule.
*
* @return string|null
* @return string
*/
public function get_parent_slug(): ?string {
$parent = '{{parent_slug}}';
return '' === $parent ? null : $parent;
public function get_parent_slug(): string {
return '{{parent_slug}}';
}

{{/parent_slug}}
/**
* Capability required to view the page.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@ class {{class}} extends AbstractBlock {
* so the editor receives the bundled assets and not raw ES modules.
* Run `npm run build` to populate this directory.
*
* @return string|null
* Narrowed to a non-null `string` (the parent declares `?string`): this
* override always returns a concrete path, and an always-non-null `?string`
* trips PHPStan level 5's checkTooWideReturnTypesInProtectedAndPublicMethods.
*
* @return string
*/
public function get_block_dir(): ?string {
public function get_block_dir(): string {
return dirname( __DIR__, 2 ) . '/{{build_dir}}/{{slug}}';
}

Expand Down
2 changes: 1 addition & 1 deletion node-packages/wp-tooling/scaffolds/wp/rest/scaffold.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
},
{
"key": "rest_namespace",
"description": "REST namespace prefix used in the URL path, e.g. 'myplugin'. Final route becomes /wp-json/{rest_namespace}/v{version}/{name}.",
"description": "REST namespace prefix used in the URL path, base only and WITHOUT the version segment, e.g. 'myplugin' (NOT 'myplugin/v1'). The version is added separately from rest_version, so the final route is /wp-json/{rest_namespace}/v{rest_version}/{name}. Passing a version here produces a doubled '/v1/v1'.",
"required": true
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ declare(strict_types=1);
namespace {{namespace}};

use rtCamp\WPFramework\Contracts\Abstracts\AbstractRESTController;
use WP_REST_Request;
use WP_REST_Response;

/**
* Registers REST routes for the {{name}} resource.
Expand Down Expand Up @@ -65,23 +63,31 @@ class {{class}}Controller extends AbstractRESTController {
/**
* Return the collection of items.
*
* @param WP_REST_Request $request The REST request.
* Overrides WP_REST_Controller::get_items(), whose signature is untyped, so
* the parameter and return stay untyped here (a typed override is a fatal
* LSP violation that aborts wp-env provisioning). Types live in PHPDoc, and
* WP-global classes are fully qualified with no `use` (so `composer format`
* does not strip a docblock-only import and break PHPStan).
*
* @return WP_REST_Response
* @param \WP_REST_Request $request The REST request.
*
* @return \WP_REST_Response
*/
public function get_items( WP_REST_Request $request ): WP_REST_Response {
public function get_items( $request ) {
// Replace with your collection response.
return new WP_REST_Response( [], 200 );
return new \WP_REST_Response( [], 200 );
}

/**
* Permission check for the collection endpoint.
*
* @param WP_REST_Request $request The REST request.
* Untyped for the same override-compatibility reason as get_items().
*
* @param \WP_REST_Request $request The REST request.
*
* @return bool
*/
public function get_items_permissions_check( WP_REST_Request $request ): bool {
public function get_items_permissions_check( $request ) {
return current_user_can( 'read' );
}
}
10 changes: 6 additions & 4 deletions node-packages/wp-tooling/skills/scaffold/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ Write a test-case checklist covering:
- `wp/cron`: `wp_next_scheduled()`, callback fires, unschedule works.
- `wp/cli`: `WP_CLI::add_command` registered, `__invoke` behaviour, dry-run flag.

The engine's shipped test file already covers **Integration** for a plain instance of the kind (e.g. `post_type_exists()` for `wp/cpt` ships written and passing, not as a stub) — list it to confirm coverage, not to write it. Your effort in §7 goes to **Happy path / Edge cases / Error paths**: brief-specific behaviour the engine can't know.

Show the checklist to the developer. Ask: confirm, add, remove? Resolve before scaffolding. This is the cheapest place to catch a misread requirement.

### 5. Apply conventions, invoke the engine
Expand Down Expand Up @@ -127,7 +129,7 @@ Result shape: `{ scaffold, engine, developer, ai, warnings }`.
| `developer.install.composer` / `developer.install.npm` | Print as copy-paste command. **Never run `composer require` / `npm install`.** |
| `developer.secrets` | Print as `gh secret set` checklist. **Never read/write/log/transmit values.** |
| `ai.wiring` | Adaptive wiring with consent (see 6a). |
| `ai.tests` | Mandatory expansion under TDD loop (see 7). |
| `ai.tests` | Shipped complete + passing for the generic pattern; add brief-specific methods under the TDD loop (see 7). |
| `warnings` | Print to developer. |

#### 6a. Adaptive wiring
Expand All @@ -147,8 +149,8 @@ For block scaffolds, surface a developer action before testing: "run `npm run bu

| Step | Action |
|---|---|
| A | Expand the engine's stub into the full suite from §4's checklist. Strip every `markTestIncomplete`. |
| B | Run: `composer test` / `composer test:unit` (PHP), `npm run test:js` / `npx jest` (JS). Expect red. If the runner errors before running, invoke the relevant `setup/*` scaffold and retry. |
| A | Confirm the engine's shipped tests pass as-is (they cover §4's Integration row already, complete and green — not a step you perform). Write one new test method per remaining §4 row: the brief-specific behaviour the engine couldn't know. |
| B | Run: `composer test` / `composer test:unit` (PHP), `npm run test:js` / `npx jest` (JS). Expect red only for the methods just added — the shipped tests stay green throughout; one going red means you broke the generic pattern, so stop and investigate. Confirm each red is an assertion failure, not a bootstrap/fatal error (env or wiring trouble isn't a valid TDD red). Runner errors before running invoke the relevant `setup/*` scaffold and retry. |
| C | Implement just enough production code to flip **one** failing test green. |
| D | Re-run. Confirm that one test passes. |
| E | Loop B-D one test at a time. |
Expand Down Expand Up @@ -186,7 +188,7 @@ Escalation report format: **what you tried, what you observed, what's blocking,

## Hard rules - never violate

- Never write production code before its test exists on disk.
- Never hand-write behaviour code before its test exists on disk. (The engine's scaffolded class + test ship together, already passing, for the generic pattern — you didn't author it, so it's not a violation. The rule governs the brief-specific behaviour you add: test first, confirm red, then extend. Consented §6a wiring is likewise sanctioned, not authored behaviour.)
- Never hand-write an artifact the engine can scaffold.
- Never group multiple kinds under a per-feature folder (`Modules/<Feature>/...`).
- Never declare an artifact done without its test file passing.
Expand Down
Loading