Skip to content

Commit

Permalink
Add config file support to @wordpress/env (#18121)
Browse files Browse the repository at this point in the history
* Add path/pathName to Context object

We will use this info to build the docker config object

* Add a utility to resolve a list of dependencies.

It resolves the dependencies from the .wpenv file and returns them in the
same "context" format as detect-context.

* Use dependency config during wp-env start

Autopopulates the docker config with volume mappings for the dependency contexts.

* Add whitespace

* Cleanup variable and file names from feedback

- Rename pathName to pathBasename
- Use .wp-env instead of .wpenv
- Inline useless function
- Clarify default param in jsdoc for detectContext

* Activate dependencies on clean action

* Don't activate deps on test environment clean

* Use wp-env.json for config file

* Use correct filename in comment

Co-Authored-By: Enrique Piqueras <[email protected]>
  • Loading branch information
noahtallen and epiqueras committed Oct 30, 2019
1 parent 96f7d34 commit 43db91b
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 11 deletions.
12 changes: 8 additions & 4 deletions packages/env/lib/create-docker-compose-config.js
Original file line number Diff line number Diff line change
@@ -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 = `
Expand Down
28 changes: 25 additions & 3 deletions packages/env/lib/detect-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,43 @@ 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' );
fileStream.on( 'data', ( text ) => {
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 );
Expand Down
14 changes: 10 additions & 4 deletions packages/env/lib/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -38,14 +39,15 @@ const setupSite = ( isTests = false ) =>
} --title=${ cwdName } --admin_user=admin --admin_password=password [email protected]`,
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 = {
Expand Down Expand Up @@ -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,
Expand All @@ -127,6 +129,7 @@ module.exports = {
await Promise.all( [
activateContext( context ),
activateContext( context, true ),
...dependencies.map( activateContext ),
] );

// Remove dangling containers and finish.
Expand All @@ -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' : ''
Expand All @@ -155,6 +160,7 @@ module.exports = {
resetDatabase()
.then( setupSite )
.then( activateContext.bind( null, context ) )
.then( activateDependencies )
.catch( () => {} )
);
}
Expand Down
44 changes: 44 additions & 0 deletions packages/env/lib/resolve-dependencies.js
Original file line number Diff line number Diff line change
@@ -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<detectContext.Context>} 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 );
};

0 comments on commit 43db91b

Please sign in to comment.