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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down
9 changes: 5 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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');
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
8 changes: 8 additions & 0 deletions templates/src/Database/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
25 changes: 23 additions & 2 deletions templates/tests/Unit/Schema_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
);

Expand All @@ -62,4 +64,23 @@ 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 {
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();

$schema = new \{{NS}}\Database\Schema();
$schema->maybe_upgrade();

$this->assertTrue( true );
}
}
12 changes: 6 additions & 6 deletions tests/generator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand All @@ -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');
Expand Down
Loading