diff --git a/.wp-env.json b/.wp-env.json index 05ea05b2809f9c..d368f3ea1c72a6 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -4,6 +4,9 @@ "plugins": [ "." ], "themes": [ "./test/emptytheme" ], "env": { + "development": { + "phpmyadminPort": 9000 + }, "tests": { "mappings": { "wp-content/plugins/gutenberg": ".", diff --git a/packages/env/CHANGELOG.md b/packages/env/CHANGELOG.md index 651d6b285e1bd6..6ee37efdf850c9 100644 --- a/packages/env/CHANGELOG.md +++ b/packages/env/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancements + +- Add phpMyAdmin as an optional service. Enabled via the new `phpmyadminPort` environment config, as well as env vars `WP_ENV_PHPMYADMIN_PORT` and `WP_ENV_TESTS_PHPMYADMIN_PORT`. + ## 10.13.0 (2024-11-27) ## 10.12.0 (2024-11-16) diff --git a/packages/env/README.md b/packages/env/README.md index 35b00fe83e8f4d..af037f5ee80ab6 100644 --- a/packages/env/README.md +++ b/packages/env/README.md @@ -349,7 +349,7 @@ containers. Positionals: container The Docker service to run the command on. [string] [required] [choices: "mysql", "tests-mysql", "wordpress", - "tests-wordpress", "cli", "tests-cli", "composer", "phpunit"] + "tests-wordpress", "cli", "tests-cli", "composer", "phpmyadmin"] command The command to run. [required] Options: diff --git a/packages/env/lib/build-docker-compose-config.js b/packages/env/lib/build-docker-compose-config.js index a394537e360872..a1a4f68256b688 100644 --- a/packages/env/lib/build-docker-compose-config.js +++ b/packages/env/lib/build-docker-compose-config.js @@ -174,6 +174,13 @@ module.exports = function buildDockerComposeConfig( config ) { config.env.tests.mysqlPort ?? '' }}:3306`; + const developmentPhpmyadminPorts = `\${WP_ENV_PHPMYADMIN_PORT:-${ + config.env.development.phpmyadminPort ?? '' + }}:80`; + const testsPhpmyadminPorts = `\${WP_ENV_TESTS_PHPMYADMIN_PORT:-${ + config.env.tests.phpmyadminPort ?? '' + }}:80`; + return { services: { mysql: { @@ -266,6 +273,26 @@ module.exports = function buildDockerComposeConfig( config ) { }, extra_hosts: [ 'host.docker.internal:host-gateway' ], }, + phpmyadmin: { + image: 'phpmyadmin', + ports: [ developmentPhpmyadminPorts ], + environment: { + PMA_PORT: 3306, + PMA_HOST: 'mysql', + PMA_USER: 'root', + PMA_PASSWORD: 'password', + }, + }, + 'tests-phpmyadmin': { + image: 'phpmyadmin', + ports: [ testsPhpmyadminPorts ], + environment: { + PMA_PORT: 3306, + PMA_HOST: 'tests-mysql', + PMA_USER: 'root', + PMA_PASSWORD: 'password', + }, + }, }, volumes: { ...( ! config.env.development.coreSource && { wordpress: {} } ), diff --git a/packages/env/lib/commands/start.js b/packages/env/lib/commands/start.js index 4203ac74632287..24d57d71fd3c62 100644 --- a/packages/env/lib/commands/start.js +++ b/packages/env/lib/commands/start.js @@ -180,6 +180,24 @@ module.exports = async function start( { } ); + if ( config.env.development.phpmyadminPort ) { + await dockerCompose.upOne( 'phpmyadmin', { + ...dockerComposeConfig, + commandOptions: shouldConfigureWp + ? [ '--build', '--force-recreate' ] + : [], + } ); + } + + if ( config.env.tests.phpmyadminPort ) { + await dockerCompose.upOne( 'tests-phpmyadmin', { + ...dockerComposeConfig, + commandOptions: shouldConfigureWp + ? [ '--build', '--force-recreate' ] + : [], + } ); + } + // Make sure we've consumed the custom CLI dockerfile. if ( shouldConfigureWp ) { await dockerCompose.buildOne( [ 'cli' ], { ...dockerComposeConfig } ); @@ -225,35 +243,61 @@ module.exports = async function start( { const siteUrl = config.env.development.config.WP_SITEURL; const testsSiteUrl = config.env.tests.config.WP_SITEURL; - const { out: mySQLAddress } = await dockerCompose.port( + const mySQLPort = await getPublicDockerPort( 'mysql', 3306, dockerComposeConfig ); - const mySQLPort = mySQLAddress.split( ':' ).pop(); - const { out: testsMySQLAddress } = await dockerCompose.port( + const testsMySQLPort = await getPublicDockerPort( 'tests-mysql', 3306, dockerComposeConfig ); - const testsMySQLPort = testsMySQLAddress.split( ':' ).pop(); - - spinner.prefixText = 'WordPress development site started' - .concat( siteUrl ? ` at ${ siteUrl }` : '.' ) - .concat( '\n' ) - .concat( 'WordPress test site started' ) - .concat( testsSiteUrl ? ` at ${ testsSiteUrl }` : '.' ) - .concat( '\n' ) - .concat( `MySQL is listening on port ${ mySQLPort }` ) - .concat( - `MySQL for automated testing is listening on port ${ testsMySQLPort }` - ) - .concat( '\n' ); + const phpmyadminPort = config.env.development.phpmyadminPort + ? await getPublicDockerPort( 'phpmyadmin', 80, dockerComposeConfig ) + : null; + + const testsPhpmyadminPort = config.env.tests.phpmyadminPort + ? await getPublicDockerPort( + 'tests-phpmyadmin', + 80, + dockerComposeConfig + ) + : null; + + spinner.prefixText = [ + 'WordPress development site started' + + ( siteUrl ? ` at ${ siteUrl }` : '.' ), + 'WordPress test site started' + + ( testsSiteUrl ? ` at ${ testsSiteUrl }` : '.' ), + `MySQL is listening on port ${ mySQLPort }`, + `MySQL for automated testing is listening on port ${ testsMySQLPort }`, + phpmyadminPort && + `phpMyAdmin started at http://localhost:${ phpmyadminPort }`, + testsPhpmyadminPort && + `phpMyAdmin for automated testing started at http://localhost:${ testsPhpmyadminPort }`, + ] + .filter( Boolean ) + .join( '\n' ); + spinner.prefixText += '\n\n'; spinner.text = 'Done!'; }; +async function getPublicDockerPort( + service, + containerPort, + dockerComposeConfig +) { + const { out: address } = await dockerCompose.port( + service, + containerPort, + dockerComposeConfig + ); + return address.split( ':' ).pop().trim(); +} + /** * Checks for legacy installs and provides * the user the option to delete them. diff --git a/packages/env/lib/config/get-config-from-environment-vars.js b/packages/env/lib/config/get-config-from-environment-vars.js index beaf721ea1c0c1..618b5fff257920 100644 --- a/packages/env/lib/config/get-config-from-environment-vars.js +++ b/packages/env/lib/config/get-config-from-environment-vars.js @@ -20,6 +20,7 @@ const { checkPort, checkVersion, checkString } = require( './validate-config' ); * @property {?number} mysqlPort An override for the development environment's MySQL port. * @property {?number} testsPort An override for the testing environment's port. * @property {?number} testsMysqlPort An override for the testing environment's MySQL port. + * @property {?number} phpmyadminPort An override for the development environment's phpMyAdmin port. * @property {?WPSource} coreSource An override for all environment's coreSource. * @property {?string} phpVersion An override for all environment's PHP version. * @property {?Object.} lifecycleScripts An override for various lifecycle scripts. @@ -40,6 +41,12 @@ module.exports = function getConfigFromEnvironmentVars( cacheDirectoryPath ) { testsMysqlPort: getPortFromEnvironmentVariable( 'WP_ENV_TESTS_MYSQL_PORT' ), + phpmyadminPort: getPortFromEnvironmentVariable( + 'WP_ENV_PHPMYADMIN_PORT' + ), + testsPhpmyadminPort: getPortFromEnvironmentVariable( + 'WP_ENV_TESTS_PHPMYADMIN_PORT' + ), lifecycleScripts: getLifecycleScriptOverrides(), }; diff --git a/packages/env/lib/config/parse-config.js b/packages/env/lib/config/parse-config.js index 1fc7e949251490..bddd7bc72aaee0 100644 --- a/packages/env/lib/config/parse-config.js +++ b/packages/env/lib/config/parse-config.js @@ -46,14 +46,15 @@ const mergeConfigs = require( './merge-configs' ); * The environment-specific configuration options. (development/tests/etc) * * @typedef WPEnvironmentConfig - * @property {WPSource} coreSource The WordPress installation to load in the environment. - * @property {WPSource[]} pluginSources Plugins to load in the environment. - * @property {WPSource[]} themeSources Themes to load in the environment. - * @property {number} port The port to use. - * @property {number} mysqlPort The port to use for MySQL. Random if empty. - * @property {Object} config Mapping of wp-config.php constants to their desired values. - * @property {Object.} mappings Mapping of WordPress directories to local directories which should be mounted. - * @property {string|null} phpVersion Version of PHP to use in the environments, of the format 0.0. + * @property {WPSource} coreSource The WordPress installation to load in the environment. + * @property {WPSource[]} pluginSources Plugins to load in the environment. + * @property {WPSource[]} themeSources Themes to load in the environment. + * @property {number} port The port to use. + * @property {number} mysqlPort The port to use for MySQL. Random if empty. + * @property {number} phpmyadminPort The port to use for phpMyAdmin. If empty, disabled phpMyAdmin. + * @property {Object} config Mapping of wp-config.php constants to their desired values. + * @property {Object.} mappings Mapping of WordPress directories to local directories which should be mounted. + * @property {string|null} phpVersion Version of PHP to use in the environments, of the format 0.0. */ /** @@ -87,6 +88,7 @@ const DEFAULT_ENVIRONMENT_CONFIG = { port: 8888, testsPort: 8889, mysqlPort: null, + phpmyadminPort: null, mappings: {}, config: { FS_METHOD: 'direct', @@ -282,6 +284,11 @@ function getEnvironmentVarOverrides( cacheDirectoryPath ) { overrideConfig.env.development.mysqlPort = overrides.mysqlPort; } + if ( overrides.phpmyadminPort ) { + overrideConfig.env.development.phpmyadminPort = + overrides.phpmyadminPort; + } + if ( overrides.testsPort ) { overrideConfig.testsPort = overrides.testsPort; overrideConfig.env.tests.port = overrides.testsPort; @@ -455,6 +462,10 @@ async function parseEnvironmentConfig( parsedConfig.mysqlPort = config.mysqlPort; } + if ( config.phpmyadminPort !== undefined ) { + parsedConfig.phpmyadminPort = config.phpmyadminPort; + } + if ( config.phpVersion !== undefined ) { // Support null as a valid input. if ( config.phpVersion !== null ) { diff --git a/packages/env/lib/config/test/__snapshots__/config-integration.js.snap b/packages/env/lib/config/test/__snapshots__/config-integration.js.snap index 6c3618f4724cb0..6b671a6bc858eb 100644 --- a/packages/env/lib/config/test/__snapshots__/config-integration.js.snap +++ b/packages/env/lib/config/test/__snapshots__/config-integration.js.snap @@ -31,6 +31,7 @@ exports[`Config Integration should load local and override configuration files 1 "mappings": {}, "mysqlPort": 23306, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 999, "themeSources": [], @@ -60,6 +61,7 @@ exports[`Config Integration should load local and override configuration files 1 "mappings": {}, "mysqlPort": 23307, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 456, "themeSources": [], @@ -106,6 +108,7 @@ exports[`Config Integration should load local configuration file 1`] = ` "mappings": {}, "mysqlPort": 13306, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 123, "themeSources": [], @@ -135,6 +138,7 @@ exports[`Config Integration should load local configuration file 1`] = ` "mappings": {}, "mysqlPort": 23307, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 8889, "themeSources": [], @@ -181,6 +185,7 @@ exports[`Config Integration should use default configuration 1`] = ` "mappings": {}, "mysqlPort": null, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 8888, "themeSources": [], @@ -210,6 +215,7 @@ exports[`Config Integration should use default configuration 1`] = ` "mappings": {}, "mysqlPort": null, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 8889, "themeSources": [], @@ -256,6 +262,7 @@ exports[`Config Integration should use environment variables over local and over "mappings": {}, "mysqlPort": 23306, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 12345, "testsPort": 61234, @@ -286,6 +293,7 @@ exports[`Config Integration should use environment variables over local and over "mappings": {}, "mysqlPort": 23307, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 61234, "testsPort": 61234, diff --git a/packages/env/lib/config/test/parse-config.js b/packages/env/lib/config/test/parse-config.js index 38e4db9860cb3c..cc6e2c7a96bbc0 100644 --- a/packages/env/lib/config/test/parse-config.js +++ b/packages/env/lib/config/test/parse-config.js @@ -22,6 +22,7 @@ const DEFAULT_CONFIG = { port: 8888, testsPort: 8889, mysqlPort: null, + phpmyadminPort: null, phpVersion: null, coreSource: { type: 'git', @@ -400,4 +401,57 @@ describe( 'parseConfig', () => { ) ); } ); + + it( 'should parse phpmyadmin configuration for a given environment', async () => { + readRawConfigFile.mockImplementation( async ( configFile ) => { + if ( configFile === '/test/gutenberg/.wp-env.json' ) { + return { + core: 'WordPress/WordPress#Test', + phpVersion: '1.0', + lifecycleScripts: { + afterStart: 'test', + }, + env: { + development: { + phpmyadminPort: 9001, + }, + }, + }; + } + } ); + + const parsed = await parseConfig( '/test/gutenberg', '/cache' ); + + const expected = { + development: { + ...DEFAULT_CONFIG.env.development, + phpmyadminPort: 9001, + }, + tests: DEFAULT_CONFIG.env.tests, + }; + expect( parsed.env ).toEqual( expected ); + } ); + + it( 'should ignore root-level configuration for phpmyadmin', async () => { + readRawConfigFile.mockImplementation( async ( configFile ) => { + if ( configFile === '/test/gutenberg/.wp-env.json' ) { + return { + core: 'WordPress/WordPress#Test', + phpVersion: '1.0', + lifecycleScripts: { + afterStart: 'test', + }, + phpmyadminPort: 9001, + }; + } + } ); + + const parsed = await parseConfig( '/test/gutenberg', '/cache' ); + + const expected = { + development: DEFAULT_CONFIG.env.development, + tests: DEFAULT_CONFIG.env.tests, + }; + expect( parsed.env ).toEqual( expected ); + } ); } ); diff --git a/packages/env/lib/validate-run-container.js b/packages/env/lib/validate-run-container.js index 77e68d2a3a4dc7..fbb6670f6c1500 100644 --- a/packages/env/lib/validate-run-container.js +++ b/packages/env/lib/validate-run-container.js @@ -10,6 +10,7 @@ const RUN_CONTAINERS = [ 'tests-wordpress', 'cli', 'tests-cli', + 'phpmyadmin', ]; /** diff --git a/schemas/json/wp-env.json b/schemas/json/wp-env.json index 491d1f8cf73017..8aa604ed41ed1f 100644 --- a/schemas/json/wp-env.json +++ b/schemas/json/wp-env.json @@ -62,6 +62,10 @@ "description": "Mapping of WordPress directories to local directories to be mounted in the WordPress instance.", "type": "object", "default": {} + }, + "phpmyadminPort": { + "description": "The port number to access phpMyAdmin.", + "type": "integer" } } }, @@ -73,7 +77,8 @@ "themes", "port", "config", - "mappings" + "mappings", + "phpmyadminPort" ] } },