diff --git a/apps/cli/lib/run-wp-cli-command.ts b/apps/cli/lib/run-wp-cli-command.ts index 92f15a1cfa..897f99a10f 100644 --- a/apps/cli/lib/run-wp-cli-command.ts +++ b/apps/cli/lib/run-wp-cli-command.ts @@ -41,6 +41,7 @@ import { } from './native-php/php-process'; import { loadImportedRuntimeStartOptionsNative } from './pull/runtime-start-options'; import { isServerRunning, sendWpCliCommand } from './wordpress-server-manager'; +import { getWpCliPhpIniArgs, WP_CLI_PHP_INI_ENTRIES } from './wp-cli-php-ini'; import { stripLeadingShebang } from './wp-cli-shebang'; import type { SiteData } from 'cli/lib/cli-config/core'; import type { ReadableStream as WebReadableStream } from 'node:stream/web'; @@ -197,7 +198,13 @@ async function runNativeWpCliCommand( const nativeArgs = applyWpCliCommandOptions( 'native', args, options ); const child = spawn( getPhpBinaryPath( phpVersion ), - [ ...defaultArgs, getWpCliPharPath(), `--path=${ site.path }`, ...nativeArgs ], + [ + ...defaultArgs, + ...getWpCliPhpIniArgs(), + getWpCliPharPath(), + `--path=${ site.path }`, + ...nativeArgs, + ], { cwd: site.path, stdio: options.stdio === 'inherit' ? 'inherit' : [ 'ignore', 'pipe', 'pipe' ], @@ -318,6 +325,7 @@ export async function runWpCliCommand( 'openssl.cafile': '/tmp/ca-bundle.crt', 'curl.cainfo': '/tmp/ca-bundle.crt', allow_url_fopen: 1, + ...WP_CLI_PHP_INI_ENTRIES, } ); await php.setSpawnHandler( createNoopSpawnHandler() ); diff --git a/apps/cli/lib/tests/wp-cli-php-ini.test.ts b/apps/cli/lib/tests/wp-cli-php-ini.test.ts new file mode 100644 index 0000000000..861b476b14 --- /dev/null +++ b/apps/cli/lib/tests/wp-cli-php-ini.test.ts @@ -0,0 +1,37 @@ +import { Readable } from 'node:stream'; +import { describe, expect, it } from 'vitest'; +import { WpCliResponse } from 'cli/lib/run-wp-cli-command'; +import { getWpCliPhpIniArgs, WP_CLI_PHP_INI_ENTRIES } from 'cli/lib/wp-cli-php-ini'; + +const PHP_85_DEPRECATION = + 'Deprecated: Case statements followed by a semicolon (;) are deprecated, use a colon (:) instead in phar:///wp-cli.phar/vendor/react/promise/src/functions.php on line 369\n'; +const JSON_STDOUT = '[{"name":"akismet","status":"active"}]\n'; + +describe( 'WP-CLI PHP ini policy', () => { + it( 'configures native -d arguments that route diagnostics to stderr', () => { + expect( getWpCliPhpIniArgs() ).toEqual( [ + '-d', + `error_reporting=${ WP_CLI_PHP_INI_ENTRIES.error_reporting }`, + '-d', + 'display_errors=stderr', + '-d', + 'log_errors=0', + ] ); + expect( Number( WP_CLI_PHP_INI_ENTRIES.error_reporting ) ).toBe( 32767 & ~8192 ); + } ); + + it( 'keeps JSON stdout parseable when PHP diagnostics are on stderr', async () => { + expect( () => JSON.parse( `${ PHP_85_DEPRECATION }${ JSON_STDOUT }` ) ).toThrow(); + + const response = new WpCliResponse( + Readable.from( [ Buffer.from( JSON_STDOUT ) ] ), + Readable.from( [ Buffer.from( PHP_85_DEPRECATION ) ] ), + Promise.resolve( 0 ) + ); + + expect( JSON.parse( await response.stdoutText ) ).toEqual( [ + { name: 'akismet', status: 'active' }, + ] ); + expect( await response.stderrText ).toBe( PHP_85_DEPRECATION ); + } ); +} ); diff --git a/apps/cli/lib/wp-cli-php-ini.ts b/apps/cli/lib/wp-cli-php-ini.ts new file mode 100644 index 0000000000..0f1866ab12 --- /dev/null +++ b/apps/cli/lib/wp-cli-php-ini.ts @@ -0,0 +1,15 @@ +const E_ALL = 32767; +const E_DEPRECATED = 8192; + +export const WP_CLI_PHP_INI_ENTRIES = { + error_reporting: String( E_ALL & ~E_DEPRECATED ), + display_errors: 'stderr', + log_errors: 0, +} as const; + +export function getWpCliPhpIniArgs(): string[] { + return Object.entries( WP_CLI_PHP_INI_ENTRIES ).flatMap( ( [ key, value ] ) => [ + '-d', + `${ key }=${ value }`, + ] ); +}