diff --git a/packages/env/lib/create-docker-compose-config.js b/packages/env/lib/create-docker-compose-config.js index f5971723148b57..84ab2490828aa0 100644 --- a/packages/env/lib/create-docker-compose-config.js +++ b/packages/env/lib/create-docker-compose-config.js @@ -1,11 +1,15 @@ module.exports = function createDockerComposeConfig( - cwd, - cwdName, cwdTestsPath, - context + context, + dependencies, ) { + const { path: cwd, pathBasename: cwdName } = context; + + const dependencyMappings = [ ...dependencies, context ].map( + ( { path, pathBasename, type } ) => ` - ${ path }/:/var/www/html/wp-content/${ type }s/${ pathBasename }/\n` + ).join( '' ); const commonVolumes = ` - - ${ cwd }/:/var/www/html/wp-content/${ context.type }s/${ cwdName }/ +${ dependencyMappings } - ${ cwd }${ cwdTestsPath }/e2e-tests/mu-plugins/:/var/www/html/wp-content/mu-plugins/ - ${ cwd }${ cwdTestsPath }/e2e-tests/plugins/:/var/www/html/wp-content/plugins/${ cwdName }-test-plugins/`; const volumes = ` diff --git a/packages/env/lib/detect-context.js b/packages/env/lib/detect-context.js index afc23f6dd5f6f3..8e1f9551b659d3 100644 --- a/packages/env/lib/detect-context.js +++ b/packages/env/lib/detect-context.js @@ -13,14 +13,34 @@ const path = require( 'path' ); const readDir = util.promisify( fs.readdir ); const finished = util.promisify( stream.finished ); -module.exports = async function detectContext() { +/** + * @typedef Context + * @type {Object} + * @property {string} type + * @property {string} path + * @property {string} pathBasename + */ + +/** + * Detects the context of a given path. + * + * @param {string} [directoryPath=process.cwd()] The directory to detect. Should point to a directory, defaulting to the current working directory. + * + * @return {Context} The context of the directory. If a theme or plugin, the type property will contain 'theme' or 'plugin'. + */ +module.exports = async function detectContext( directoryPath = process.cwd() ) { const context = {}; + // Use absolute paths to files so that we can properly read + // dependencies not in the current working directory. + const absPath = path.resolve( directoryPath ); + // Race multiple file read streams against each other until // a plugin or theme header is found. - const files = ( await readDir( './' ) ).filter( + const files = ( await readDir( absPath ) ).filter( ( file ) => path.extname( file ) === '.php' || path.basename( file ) === 'style.css' - ); + ).map( ( fileName ) => path.join( absPath, fileName ) ); + const streams = []; for ( const file of files ) { const fileStream = fs.createReadStream( file, 'utf8' ); @@ -28,6 +48,8 @@ module.exports = async function detectContext() { const [ , type ] = text.match( /(Plugin|Theme) Name: .*[\r\n]/ ) || []; if ( type ) { context.type = type.toLowerCase(); + context.path = absPath; + context.pathBasename = path.basename( absPath ); // Stop the creation of new streams by mutating the iterated array. We can't `break`, because we are inside a function. files.splice( 0 ); diff --git a/packages/env/lib/env.js b/packages/env/lib/env.js index c2ba83ac24c221..232fba9910cdf6 100644 --- a/packages/env/lib/env.js +++ b/packages/env/lib/env.js @@ -11,8 +11,9 @@ const wait = require( 'util' ).promisify( setTimeout ); /** * Internal dependencies */ -const detectContext = require( './detect-context' ); const createDockerComposeConfig = require( './create-docker-compose-config' ); +const detectContext = require( './detect-context' ); +const resolveDependencies = require( './resolve-dependencies' ); // Config Variables const cwd = process.cwd(); @@ -38,14 +39,15 @@ const setupSite = ( isTests = false ) => } --title=${ cwdName } --admin_user=admin --admin_password=password --admin_email=admin@wordpress.org`, isTests ); -const activateContext = ( context, isTests = false ) => - wpCliRun( `wp ${ context.type } activate ${ cwdName }`, isTests ); +const activateContext = ( { type, pathBasename }, isTests = false ) => + wpCliRun( `wp ${ type } activate ${ pathBasename }`, isTests ); const resetDatabase = ( isTests = false ) => wpCliRun( 'wp db reset --yes', isTests ); module.exports = { async start( { ref, spinner = {} } ) { const context = await detectContext(); + const dependencies = await resolveDependencies(); spinner.text = `Downloading WordPress@${ ref } 0/100%.`; const gitFetchOptions = { @@ -100,7 +102,7 @@ module.exports = { spinner.text = `Starting WordPress@${ ref }.`; fs.writeFileSync( dockerComposeOptions.config, - createDockerComposeConfig( cwd, cwdName, cwdTestsPath, context ) + createDockerComposeConfig( cwdTestsPath, context, dependencies ) ); // These will bring up the database container, @@ -127,6 +129,7 @@ module.exports = { await Promise.all( [ activateContext( context ), activateContext( context, true ), + ...dependencies.map( activateContext ), ] ); // Remove dangling containers and finish. @@ -142,6 +145,8 @@ module.exports = { async clean( { environment, spinner } ) { const context = await detectContext(); + const dependencies = await resolveDependencies(); + const activateDependencies = () => Promise.all( dependencies.map( activateContext ) ); const description = `${ environment } environment${ environment === 'all' ? 's' : '' @@ -155,6 +160,7 @@ module.exports = { resetDatabase() .then( setupSite ) .then( activateContext.bind( null, context ) ) + .then( activateDependencies ) .catch( () => {} ) ); } diff --git a/packages/env/lib/resolve-dependencies.js b/packages/env/lib/resolve-dependencies.js new file mode 100644 index 00000000000000..4ff1205acd8d50 --- /dev/null +++ b/packages/env/lib/resolve-dependencies.js @@ -0,0 +1,44 @@ +'use strict'; + +/** + * External dependencies + */ +const util = require( 'util' ); +const fs = require( 'fs' ); + +/** + * Internal dependencies + */ +const detectContext = require( './detect-context' ); + +/** + * Promisified dependencies + */ +const readFile = util.promisify( fs.readFile ); + +/** + * Returns an array of dependencies to be mounted in the Docker image. + * + * Reads from the wp-env.json file in the current directory and uses detect + * context to make sure the specified dependencies exist and are plugins + * and/or themes. + * + * @return {Array} An array of dependencies in the context format. + */ +module.exports = async function resolveDependencies() { + const envFile = await readFile( './wp-env.json' ); + const { themes, plugins } = JSON.parse( envFile ); + + const dependencyResolvers = []; + if ( Array.isArray( themes ) ) { + dependencyResolvers.push( ...themes.map( detectContext ) ); + } + + if ( Array.isArray( plugins ) ) { + dependencyResolvers.push( ...plugins.map( detectContext ) ); + } + + // Return all dependencies which have been detected to be a plugin or a theme. + const dependencies = await Promise.all( dependencyResolvers ); + return dependencies.filter( ( { type } ) => !! type ); +};