@wordpress/env
Version:
A zero-config, self contained local WordPress environment for development and testing.
307 lines (290 loc) • 8.82 kB
JavaScript
;
/**
* External dependencies
*/
const fs = require( 'fs' );
const path = require( 'path' );
/**
* Internal dependencies
*/
const { hasSameCoreSource } = require( './wordpress' );
const { dbEnv } = require( './config' );
const getHostUser = require( './get-host-user' );
/**
* @typedef {import('./config').WPConfig} WPConfig
* @typedef {import('./config').WPEnvironmentConfig} WPEnvironmentConfig
*/
/**
* Gets the volume mounts for an individual service.
*
* @param {string} workDirectoryPath The working directory for wp-env.
* @param {WPEnvironmentConfig} config The service config to get the mounts from.
* @param {string} hostUsername The username of the host running wp-env.
* @param {string} wordpressDefault The default internal path for the WordPress
* source code (such as tests-wordpress).
*
* @return {string[]} An array of volumes to mount in string format.
*/
function getMounts(
workDirectoryPath,
config,
hostUsername,
wordpressDefault = 'wordpress'
) {
// Top-level WordPress directory mounts (like wp-content/themes)
const directoryMounts = Object.entries( config.mappings ).map(
( [ wpDir, source ] ) => `${ source.path }:/var/www/html/${ wpDir }`
);
const pluginMounts = config.pluginSources.map(
( source ) =>
`${ source.path }:/var/www/html/wp-content/plugins/${ source.basename }`
);
const themeMounts = config.themeSources.map(
( source ) =>
`${ source.path }:/var/www/html/wp-content/themes/${ source.basename }`
);
const userHomeMount =
wordpressDefault === 'wordpress'
? `user-home:/home/${ hostUsername }`
: `tests-user-home:/home/${ hostUsername }`;
const corePHPUnitMount = `${ path.join(
workDirectoryPath,
wordpressDefault === 'wordpress'
? 'WordPress-PHPUnit'
: 'tests-WordPress-PHPUnit',
'tests',
'phpunit'
) }:/wordpress-phpunit`;
const coreMount = `${
config.coreSource ? config.coreSource.path : wordpressDefault
}:/var/www/html`;
return [
...new Set( [
coreMount, // Must be first because of some operations later that expect it to be!
corePHPUnitMount,
userHomeMount,
...directoryMounts,
...pluginMounts,
...themeMounts,
] ),
];
}
/**
* Creates a docker-compose config object which, when serialized into a
* docker-compose.yml file, tells docker-compose how to run the environment.
*
* @param {WPConfig} config A wp-env config object.
*
* @return {Object} A docker-compose config object, ready to serialize into YAML.
*/
module.exports = function buildDockerComposeConfig( config ) {
// Since we are mounting files from the host operating system
// we want to create the host user in some of our containers.
// This ensures ownership parity and lets us access files
// and folders between the containers and the host.
const hostUser = getHostUser();
const developmentMounts = getMounts(
config.workDirectoryPath,
config.env.development,
hostUser.name
);
const testsMounts = getMounts(
config.workDirectoryPath,
config.env.tests,
hostUser.name,
'tests-wordpress'
);
// We use a custom Dockerfile in order to make sure that
// the current host user exists inside the container.
const imageBuildArgs = {
HOST_USERNAME: hostUser.name,
HOST_UID: hostUser.uid,
HOST_GID: hostUser.gid,
};
// When both tests and development reference the same WP source, we need to
// ensure that tests pulls from a copy of the files so that it maintains
// a separate DB and config. Additionally, if the source type is local we
// need to ensure:
//
// 1. That changes the user makes within the "core" directory are
// served in both the development and tests environments.
// 2. That the development and tests environment use separate
// databases and `wp-content/uploads`.
//
// To do this we copy the local "core" files ($wordpress) to a tests
// directory ($tests-wordpress) and instruct the tests environment
// to source its files like so:
//
// - wp-config.php <- $tests-wordpress/wp-config.php
// - wp-config-sample.php <- $tests-wordpress/wp-config.php
// - wp-content <- $tests-wordpress/wp-content
// - * <- $wordpress/*
//
// https://github.com/WordPress/gutenberg/issues/21164
if (
config.env.development.coreSource &&
hasSameCoreSource( [ config.env.development, config.env.tests ] )
) {
const wpSource = config.env.development.coreSource;
testsMounts.shift(); // Remove normal core mount.
testsMounts.unshift(
...[
`${ wpSource.testsPath }:/var/www/html`,
...( wpSource.type === 'local'
? fs
.readdirSync( wpSource.path )
.filter(
( filename ) =>
filename !== 'wp-config.php' &&
filename !== 'wp-config-sample.php' &&
filename !== 'wp-content'
)
.map(
( filename ) =>
`${ path.join(
wpSource.path,
filename
) }:/var/www/html/${ filename }`
)
: [] ),
]
);
}
// Set the default ports based on the config values.
const developmentPorts = `\${WP_ENV_PORT:-${ config.env.development.port }}:80`;
const developmentMysqlPorts = `\${WP_ENV_MYSQL_PORT:-${
config.env.development.mysqlPort ?? ''
}}:3306`;
const testsPorts = `\${WP_ENV_TESTS_PORT:-${ config.env.tests.port }}:80`;
const testsMysqlPorts = `\${WP_ENV_TESTS_MYSQL_PORT:-${
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: {
image: 'mariadb:lts',
ports: [ developmentMysqlPorts ],
environment: {
MYSQL_ROOT_HOST: '%',
MYSQL_ROOT_PASSWORD:
dbEnv.credentials.WORDPRESS_DB_PASSWORD,
MYSQL_DATABASE: dbEnv.development.WORDPRESS_DB_NAME,
},
volumes: [ 'mysql:/var/lib/mysql' ],
},
'tests-mysql': {
image: 'mariadb:lts',
ports: [ testsMysqlPorts ],
environment: {
MYSQL_ROOT_HOST: '%',
MYSQL_ROOT_PASSWORD:
dbEnv.credentials.WORDPRESS_DB_PASSWORD,
MYSQL_DATABASE: dbEnv.tests.WORDPRESS_DB_NAME,
},
volumes: [ 'mysql-test:/var/lib/mysql' ],
},
wordpress: {
depends_on: [ 'mysql' ],
build: {
context: '.',
dockerfile: 'WordPress.Dockerfile',
args: imageBuildArgs,
},
ports: [ developmentPorts ],
environment: {
APACHE_RUN_USER: '#' + hostUser.uid,
APACHE_RUN_GROUP: '#' + hostUser.gid,
...dbEnv.credentials,
...dbEnv.development,
WP_TESTS_DIR: '/wordpress-phpunit',
},
volumes: developmentMounts,
extra_hosts: [ 'host.docker.internal:host-gateway' ],
},
'tests-wordpress': {
depends_on: [ 'tests-mysql' ],
build: {
context: '.',
dockerfile: 'Tests-WordPress.Dockerfile',
args: imageBuildArgs,
},
ports: [ testsPorts ],
environment: {
APACHE_RUN_USER: '#' + hostUser.uid,
APACHE_RUN_GROUP: '#' + hostUser.gid,
...dbEnv.credentials,
...dbEnv.tests,
WP_TESTS_DIR: '/wordpress-phpunit',
},
volumes: testsMounts,
extra_hosts: [ 'host.docker.internal:host-gateway' ],
},
cli: {
depends_on: [ 'wordpress' ],
build: {
context: '.',
dockerfile: 'CLI.Dockerfile',
args: imageBuildArgs,
},
volumes: developmentMounts,
user: hostUser.fullUser,
environment: {
...dbEnv.credentials,
...dbEnv.development,
WP_TESTS_DIR: '/wordpress-phpunit',
},
extra_hosts: [ 'host.docker.internal:host-gateway' ],
},
'tests-cli': {
depends_on: [ 'tests-wordpress' ],
build: {
context: '.',
dockerfile: 'Tests-CLI.Dockerfile',
args: imageBuildArgs,
},
volumes: testsMounts,
user: hostUser.fullUser,
environment: {
...dbEnv.credentials,
...dbEnv.tests,
WP_TESTS_DIR: '/wordpress-phpunit',
},
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: {} } ),
...( ! config.env.tests.coreSource && { 'tests-wordpress': {} } ),
mysql: {},
'mysql-test': {},
'user-home': {},
'tests-user-home': {},
},
};
};