From dbe9ad4acf3de029df234c8061ee2ec0ff19aeef Mon Sep 17 00:00:00 2001 From: Akshat Date: Sat, 29 Aug 2026 20:32:56 +0530 Subject: [PATCH 1/2] chore(cli): PHP floor 8.2, v3.0.0, gate Schema::maybe_upgrade() (re-audit) - MIN_PHP 8.3 -> 8.2: 8.1 is the newest syntax the templates use; 8.2 is the oldest line still getting security fixes and covers the widest install base. CI matrix + phpcs testVersion + own CI jobs + README follow. - package.json 2.0.0 -> 3.0.0: the merged PHP pin and the static-bootloader rewrite are both breaking, and the published version was still 2.0.0. - Schema::maybe_upgrade() (#13): bail unless is_admin() || wp_doing_cron() || WP_CLI, so dbDelta() never runs on a cached front-end request. New Schema_Test case covers the front-end skip. Verified: 67 generator + 13 engine tests; 8.2 scaffold php -l clean, version tokens all 8.2. --- .github/workflows/ci.yml | 4 ++-- README.md | 6 +++--- index.js | 9 +++++---- package.json | 2 +- templates/src/Database/Schema.php | 8 ++++++++ templates/tests/Unit/Schema_Test.php | 21 +++++++++++++++++++-- tests/generator.test.js | 12 ++++++------ 7 files changed, 44 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 472e840..a7dbbf0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.3' + php-version: '8.2' tools: composer coverage: none @@ -150,7 +150,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.3' + php-version: '8.2' tools: composer coverage: none diff --git a/README.md b/README.md index 35c5e0f..5fa4bec 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ created. `--author`, `--email`, `--author-uri`, `--description`, `--out`, `--modules`, `--react`, `--no-react`, `--lint-target`. -Generated plugins target **PHP 8.3** — this is fixed, not configurable. The +Generated plugins target **PHP 8.2** — this is fixed, not configurable. The output uses constructor property promotion, `readonly` properties, and first-class callable syntax throughout. @@ -157,8 +157,8 @@ npm run build - `composer lint` runs `WordPress-Extra` + `WordPress-Docs` (`wp-org`), `WordPress-VIP-Go` (`vip`), or both, plus `PHPCompatibilityWP` against - `testVersion 8.3-`. Generated code passes with no blanket `phpcs:ignore`s. -- The generated `.github/workflows/ci.yml`: PHPCS, a PHPUnit matrix (8.3, 8.4), a + `testVersion 8.2-`. Generated code passes with no blanket `phpcs:ignore`s. +- The generated `.github/workflows/ci.yml`: PHPCS, a PHPUnit matrix (8.2, 8.3, 8.4), a `node-build` job (build + Jest + Playwright) when there's a pipeline, JS/CSS lint, and an `integration` job with the `integration_tests` module. diff --git a/index.js b/index.js index 30670ed..6488fc9 100644 --- a/index.js +++ b/index.js @@ -316,9 +316,10 @@ export function validateModules(modules) { } // The generated code targets a single modern PHP baseline — constructor -// property promotion, readonly properties, first-class callable syntax — and -// this is not configurable: every scaffold requires PHP 8.3. -export const MIN_PHP = '8.3'; +// property promotion, readonly properties, first-class callable syntax (all +// PHP 8.1) — pinned, not configurable. 8.2 is the floor: it's the oldest line +// still getting security fixes, and matches the widest install base. +export const MIN_PHP = '8.2'; export function validateOutputDir(val) { if (!val || typeof val !== 'string' || val.trim().length === 0) { @@ -1377,7 +1378,7 @@ ${entries.join('\n')} // Single supported PHP line — see MIN_PHP. The matrix also runs the next // minor so a scaffold surfaces forward-compat breakage early. - const ciPhpMatrix = "['8.3', '8.4']"; + const ciPhpMatrix = "['8.2', '8.3', '8.4']"; // Process ci.yml with dynamic node job let ciContent = fs.readFileSync(path.join(templatesDir, 'github/workflows/ci.yml'), 'utf8'); diff --git a/package.json b/package.json index 16dad16..b547a47 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "create-wp-plugin-cli", - "version": "2.0.0", + "version": "3.0.0", "description": "Interactive scaffold generator for modern WordPress plugins — SOLID/DI architecture (Container + Service Providers), PSR-4, selectable WPCS/VIP standards, PHPUnit unit + integration tests, Jest, Playwright, WP-CLI, WooCommerce, Interactivity API, and CI out of the box", "main": "index.js", "bin": { diff --git a/templates/src/Database/Schema.php b/templates/src/Database/Schema.php index 232e50e..4f1c32c 100644 --- a/templates/src/Database/Schema.php +++ b/templates/src/Database/Schema.php @@ -64,9 +64,17 @@ public function init_hooks(): void { /** * Run create_table() again if the stored schema version is behind VERSION. * + * Only in admin / cron / WP-CLI: dbDelta() is a heavy DESCRIBE + ALTER, + * and there's no reason to pay for it on a cached front-end request. A + * plugin update always lands via one of those contexts anyway. + * * @return void */ public function maybe_upgrade(): void { + if ( ! is_admin() && ! wp_doing_cron() && ! ( defined( 'WP_CLI' ) && WP_CLI ) ) { + return; + } + if ( get_option( self::VERSION_OPTION ) === self::VERSION ) { return; } diff --git a/templates/tests/Unit/Schema_Test.php b/templates/tests/Unit/Schema_Test.php index 8c87a30..80cd418 100644 --- a/templates/tests/Unit/Schema_Test.php +++ b/templates/tests/Unit/Schema_Test.php @@ -48,12 +48,14 @@ public function test_table_name(): void { } /** - * Test maybe_upgrade skips when version is current. + * Test maybe_upgrade skips when version is current (in an admin request). */ public function test_maybe_upgrade_skips_when_version_matches(): void { Functions\stubs( array( - 'get_option' => \{{NS}}\Database\Schema::VERSION, + 'is_admin' => true, + 'wp_doing_cron' => false, + 'get_option' => \{{NS}}\Database\Schema::VERSION, ) ); @@ -62,4 +64,19 @@ public function test_maybe_upgrade_skips_when_version_matches(): void { $this->assertTrue( true ); } + + /** + * Test maybe_upgrade does not even read the version option on a plain + * front-end request — dbDelta() is admin/cron/CLI-only. + */ + public function test_maybe_upgrade_skips_on_a_frontend_request(): void { + Functions\when( 'is_admin' )->justReturn( false ); + Functions\when( 'wp_doing_cron' )->justReturn( false ); + Functions\expect( 'get_option' )->never(); + + $schema = new \{{NS}}\Database\Schema(); + $schema->maybe_upgrade(); + + $this->assertTrue( true ); + } } diff --git a/tests/generator.test.js b/tests/generator.test.js index 04d76d4..e3a3471 100644 --- a/tests/generator.test.js +++ b/tests/generator.test.js @@ -197,8 +197,8 @@ test('validateModules rejects unknown module names but allows empty/known lists' assert.equal(typeof validateModules(['admin_settings', 'not_a_real_module']), 'string'); }); -test('every scaffold pins PHP 8.3 and emits modern PHP (promotion, readonly, first-class callables)', () => { - assert.equal(MIN_PHP, '8.3'); +test('every scaffold pins PHP 8.2 and emits modern PHP (promotion, readonly, first-class callables)', () => { + assert.equal(MIN_PHP, '8.2'); const outDir = path.join(__dirname, '../tmp-test-php83'); fs.rmSync(outDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); @@ -212,12 +212,12 @@ test('every scaffold pins PHP 8.3 and emits modern PHP (promotion, readonly, fir const requires = ['modern-php.php', 'composer.json', 'readme.txt'].map( (f) => fs.readFileSync(path.join(outDir, f), 'utf8') ); - assert.match(requires[0], /Requires PHP:\s+8\.3/); - assert.match(requires[1], /"php":\s*">=8\.3"/); - assert.match(requires[2], /Requires PHP: 8\.3/); + assert.match(requires[0], /Requires PHP:\s+8\.2/); + assert.match(requires[1], /"php":\s*">=8\.2"/); + assert.match(requires[2], /Requires PHP: 8\.2/); const ci = fs.readFileSync(path.join(outDir, '.github/workflows/ci.yml'), 'utf8'); - assert.match(ci, /php-version:\s*\['8\.3', '8\.4'\]/); + assert.match(ci, /php-version:\s*\['8\.2', '8\.3', '8\.4'\]/); const plugin = fs.readFileSync(path.join(outDir, 'src/Plugin.php'), 'utf8'); assert.match(plugin, /public static function instance\(\): self/, 'singleton accessor'); From f4bb574870512adf89e415c771fd0c32e8bd8e01 Mon Sep 17 00:00:00 2001 From: Akshat Date: Sat, 29 Aug 2026 20:48:10 +0530 Subject: [PATCH 2/2] test(templates): guard Schema front-end skip test against WP_CLI leaking from Commands_Test --- templates/tests/Unit/Schema_Test.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/templates/tests/Unit/Schema_Test.php b/templates/tests/Unit/Schema_Test.php index 80cd418..774121c 100644 --- a/templates/tests/Unit/Schema_Test.php +++ b/templates/tests/Unit/Schema_Test.php @@ -70,6 +70,10 @@ public function test_maybe_upgrade_skips_when_version_matches(): void { * front-end request — dbDelta() is admin/cron/CLI-only. */ public function test_maybe_upgrade_skips_on_a_frontend_request(): void { + if ( defined( 'WP_CLI' ) && WP_CLI ) { + $this->markTestSkipped( 'WP_CLI is defined in this test process; the front-end gate cannot be exercised.' ); + } + Functions\when( 'is_admin' )->justReturn( false ); Functions\when( 'wp_doing_cron' )->justReturn( false ); Functions\expect( 'get_option' )->never();