Skip to content

Commit

Permalink
Expanded wp-env Lifecycle Scripts (#50570)
Browse files Browse the repository at this point in the history
Replaced the `afterSetup` script with more granular lifecycle scripts.
Using the new config option `lifecycleScripts` you can now set
scripts for `afterStart`, `afterClean`, and `afterDestroy`.
  • Loading branch information
ObliviousHarmony authored May 23, 2023
1 parent 9c9fe12 commit a73b80e
Show file tree
Hide file tree
Showing 24 changed files with 468 additions and 238 deletions.
8 changes: 7 additions & 1 deletion packages/env/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@

### Breaking Change

- Rework`run` command to resolve bugs with non-quoted commands. As a consequence it is no longer possible to pass your entire command to `wp-env` wrapped in double-quotes. While `npx wp-env run cli wp help` will still work, `npx wp-env run cli "wp help"` will not. If you are currently escaping any quotes you will need to review those commands and ensure they are compatible with this update.
- Remove `afterSetup` option from `.wp-env.json` and the `WP_ENV_AFTER_SETUP` environment variable in favor of more granular lifecycle scripts.

### New feature

- Add `afterStart`, `afterClean`, and `afterDestroy` lifecycle scripts to a new `lifecycleScripts` key in `.wp-env.json`.
- Add a series of `WP_ENV_LIFECYCLE_SCRIPT_` environment variables for the various lifecycle scripts.
- Rework `run` command to resolve bugs with non-quoted commands. As a consequence it is no longer possible to pass your entire command to `wp-env` wrapped in double-quotes. While `npx wp-env run cli wp help` will still work, `npx wp-env run cli "wp help"` will not. If you are currently escaping any quotes you will need to review those commands and ensure they are compatible with this update.

### Enhancement

Expand Down
26 changes: 13 additions & 13 deletions packages/env/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,8 @@ Destroy the WordPress environment. Deletes docker containers, volumes, and
networks associated with the WordPress environment and removes local files.

Options:
--debug Enable debug output. [boolean] [default: false]
--debug Enable debug output. [boolean] [default: false]
--scripts Execute any configured lifecycle scripts. [boolean] [default: true]
```

### `wp-env logs [environment]`
Expand Down Expand Up @@ -557,19 +558,16 @@ These can be overridden by setting a value within the `config` configuration. Se

Additionally, the values referencing a URL include the specified port for the given environment. So if you set `testsPort: 3000, port: 2000`, `WP_HOME` (for example) will be `http://localhost:3000` on the tests instance and `http://localhost:2000` on the development instance.

## Lifecycle Hooks

These hooks are executed at certain points during the lifecycle of a command's execution. Keep in mind that these will be executed on both fresh and existing
environments, so, ensure any commands you build won't break on subsequent executions.
## Lifecycle Scripts

### After Setup
Using the `lifecycleScripts` option in `.wp-env.json` will allow you to set arbitrary commands to be executed at certain points in the lifecycle. This configuration
can also be overridden using `WP_ENV_LIFECYCLE_SCRIPT_{LIFECYCLE_EVENT}` environment variables, with the remainder being the all-caps snake_case name of the option, for
example, `WP_ENV_LIFECYCLE_SCRIPT_AFTER_START`. Keep in mind that these will be executed on both fresh and existing environments, so, ensure any commands you
build won't break on subsequent executions.

Using the `afterSetup` option in `.wp-env.json` files will allow you to configure an arbitrary command to execute after the environment's setup is complete:

- `wp-env start`: Runs when the config changes, WordPress updates, or you pass the `--update` flag.
- `wp-env clean`: Runs after the selected environments have been cleaned.

You can override the `afterSetup` option using the `WP_ENV_AFTER_SETUP` environment variable.
* `afterStart`: Runs after `wp-env start` has finished setting up the environment.
* `afterClean`: Runs after `wp-env clean` has finished cleaning the environment.
* `afterDestroy`: Runs after `wp-env destroy` has destroyed the environment.

## Examples

Expand Down Expand Up @@ -705,7 +703,9 @@ This is useful for performing some actions after setting up the environment, suc

```json
{
"afterSetup": "node tests/e2e/bin/setup-env.js"
"lifecycleScripts": {
"afterStart": "node tests/e2e/bin/setup-env.js"
}
}
```

Expand Down
10 changes: 8 additions & 2 deletions packages/env/lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const withSpinner =
( error ) => {
if (
error instanceof env.ValidationError ||
error instanceof env.AfterSetupError
error instanceof env.LifecycleScriptError
) {
// Error is a configuration error. That means the user did something wrong.
spinner.fail( error.message );
Expand Down Expand Up @@ -233,7 +233,13 @@ module.exports = function cli() {
wpRed(
'Destroy the WordPress environment. Deletes docker containers, volumes, and networks associated with the WordPress environment and removes local files.'
),
() => {},
( args ) => {
args.option( 'scripts', {
type: 'boolean',
describe: 'Execute any configured lifecycle scripts.',
default: true,
} );
},
withSpinner( env.destroy )
);
yargs.command(
Expand Down
5 changes: 2 additions & 3 deletions packages/env/lib/commands/clean.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const dockerCompose = require( 'docker-compose' );
*/
const initConfig = require( '../init-config' );
const { configureWordPress, resetDatabase } = require( '../wordpress' );
const { executeAfterSetup } = require( '../execute-after-setup' );
const { executeLifecycleScript } = require( '../execute-lifecycle-script' );

/**
* @typedef {import('../wordpress').WPEnvironment} WPEnvironment
Expand Down Expand Up @@ -65,9 +65,8 @@ module.exports = async function clean( {

await Promise.all( tasks );

// Execute any configured command that should run after the environment has finished being set up.
if ( scripts ) {
executeAfterSetup( config, spinner );
await executeLifecycleScript( 'afterClean', config, spinner );
}

spinner.text = `Cleaned ${ description }.`;
Expand Down
18 changes: 11 additions & 7 deletions packages/env/lib/commands/destroy.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,21 @@ const rimraf = util.promisify( require( 'rimraf' ) );
* Internal dependencies
*/
const { loadConfig } = require( '../config' );
const { executeLifecycleScript } = require( '../execute-lifecycle-script' );

/**
* Destroy the development server.
*
* @param {Object} options
* @param {Object} options.spinner A CLI spinner which indicates progress.
* @param {boolean} options.scripts Indicates whether or not lifecycle scripts should be executed.
* @param {boolean} options.debug True if debug mode is enabled.
*/
module.exports = async function destroy( { spinner, debug } ) {
const { dockerComposeConfigPath, workDirectoryPath } = await loadConfig(
path.resolve( '.' )
);
module.exports = async function destroy( { spinner, scripts, debug } ) {
const config = await loadConfig( path.resolve( '.' ) );

try {
await fs.readdir( workDirectoryPath );
await fs.readdir( config.workDirectoryPath );
} catch {
spinner.text = 'Could not find any files to remove.';
return;
Expand Down Expand Up @@ -60,7 +60,7 @@ module.exports = async function destroy( { spinner, debug } ) {
spinner.text = 'Removing docker images, volumes, and networks.';

await dockerCompose.down( {
config: dockerComposeConfigPath,
config: config.dockerComposeConfigPath,
commandOptions: [ '--volumes', '--remove-orphans', '--rmi', 'all' ],
log: debug,
} );
Expand All @@ -70,7 +70,11 @@ module.exports = async function destroy( { spinner, debug } ) {
// by this point, which causes rimraf to fail. We need to wait at least 2.5-5s,
// but using 10s in case it's dependant on the machine.
await new Promise( ( resolve ) => setTimeout( resolve, 10000 ) );
await rimraf( workDirectoryPath );
await rimraf( config.workDirectoryPath );

if ( scripts ) {
await executeLifecycleScript( 'afterDestroy', config, spinner );
}

spinner.text = 'Removed WordPress environment.';
};
11 changes: 5 additions & 6 deletions packages/env/lib/commands/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const {
} = require( '../wordpress' );
const { didCacheChange, setCache } = require( '../cache' );
const md5 = require( '../md5' );
const { executeAfterSetup } = require( '../execute-after-setup' );
const { executeLifecycleScript } = require( '../execute-lifecycle-script' );

/**
* @typedef {import('../config').WPConfig} WPConfig
Expand Down Expand Up @@ -203,17 +203,16 @@ module.exports = async function start( {
} ),
] );

// Execute any configured command that should run after the environment has finished being set up.
if ( scripts ) {
executeAfterSetup( config, spinner );
}

// Set the cache key once everything has been configured.
await setCache( CONFIG_CACHE_KEY, configHash, {
workDirectoryPath,
} );
}

if ( scripts ) {
await executeLifecycleScript( 'afterStart', config, spinner );
}

const siteUrl = config.env.development.config.WP_SITEURL;
const testsSiteUrl = config.env.tests.config.WP_SITEURL;

Expand Down
48 changes: 34 additions & 14 deletions packages/env/lib/config/get-config-from-environment-vars.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ const { checkPort, checkVersion, checkString } = require( './validate-config' );
* Environment variable configuration.
*
* @typedef WPEnvironmentVariableConfig
* @property {?number} port An override for the development environment's port.
* @property {?number} testsPort An override for the testing environment's port.
* @property {?WPSource} coreSource An override for all environment's coreSource.
* @property {?string} phpVersion An override for all environment's PHP version.
* @property {?number} port An override for the development environment's port.
* @property {?number} testsPort An override for the testing environment's port.
* @property {?WPSource} coreSource An override for all environment's coreSource.
* @property {?string} phpVersion An override for all environment's PHP version.
* @property {?Object.<string, string>} lifecycleScripts An override for various lifecycle scripts.
*/

/**
Expand All @@ -33,6 +34,7 @@ module.exports = function getConfigFromEnvironmentVars( cacheDirectoryPath ) {
const environmentConfig = {
port: getPortFromEnvironmentVariable( 'WP_ENV_PORT' ),
testsPort: getPortFromEnvironmentVariable( 'WP_ENV_TESTS_PORT' ),
lifecycleScripts: getLifecycleScriptOverrides(),
};

if ( process.env.WP_ENV_CORE ) {
Expand All @@ -53,15 +55,6 @@ module.exports = function getConfigFromEnvironmentVars( cacheDirectoryPath ) {
environmentConfig.phpVersion = process.env.WP_ENV_PHP_VERSION;
}

if ( process.env.WP_ENV_AFTER_SETUP ) {
checkString(
'environment variable',
'WP_ENV_AFTER_SETUP',
process.env.WP_ENV_AFTER_SETUP
);
environmentConfig.afterSetup = process.env.WP_ENV_AFTER_SETUP;
}

return environmentConfig;
};

Expand All @@ -70,7 +63,7 @@ module.exports = function getConfigFromEnvironmentVars( cacheDirectoryPath ) {
*
* @param {string} varName The environment variable to check (e.g. WP_ENV_PORT).
*
* @return {number} The parsed port number
* @return {number} The parsed port number.
*/
function getPortFromEnvironmentVariable( varName ) {
if ( ! process.env[ varName ] ) {
Expand All @@ -84,3 +77,30 @@ function getPortFromEnvironmentVariable( varName ) {

return port;
}

/**
* Parses the lifecycle script environment variables.
*
* @return {Object.<string, string>} The parsed lifecycle scripts.
*/
function getLifecycleScriptOverrides() {
const lifecycleScripts = {};

// Find all of the lifecycle script overrides and parse them.
const lifecycleEnvironmentVars = {
WP_ENV_LIFECYCLE_SCRIPT_AFTER_START: 'afterStart',
WP_ENV_LIFECYCLE_SCRIPT_AFTER_CLEAN: 'afterClean',
WP_ENV_LIFECYCLE_SCRIPT_AFTER_DESTROY: 'afterDestroy',
};
for ( const envVar in lifecycleEnvironmentVars ) {
const scriptValue = process.env[ envVar ];
if ( scriptValue === undefined ) {
continue;
}

checkString( 'environment variable', envVar, scriptValue );
lifecycleScripts[ lifecycleEnvironmentVars[ envVar ] ] = scriptValue;
}

return lifecycleScripts;
}
4 changes: 2 additions & 2 deletions packages/env/lib/config/load-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const postProcessConfig = require( './post-process-config' );
* @property {string} workDirectoryPath Path to the work directory located in ~/.wp-env.
* @property {string} dockerComposeConfigPath Path to the docker-compose.yml file.
* @property {boolean} detectedLocalConfig If true, wp-env detected local config and used it.
* @property {string} afterSetup The command(s) to run after configuring WordPress on start and clean.
* @property {Object.<string, string>} lifecycleScripts Any lifecycle scripts that we might need to execute.
* @property {Object.<string, WPEnvironmentConfig>} env Specific config for different environments.
* @property {boolean} debug True if debug mode is enabled.
*/
Expand Down Expand Up @@ -68,7 +68,7 @@ module.exports = async function loadConfig( configDirectoryPath ) {
configFilePath,
getConfigFilePath( configDirectoryPath, 'override' ),
] ),
afterSetup: config.afterSetup,
lifecycleScripts: config.lifecycleScripts,
env: config.env,
};
};
Expand Down
3 changes: 2 additions & 1 deletion packages/env/lib/config/merge-configs.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ function mergeConfig( config, toMerge ) {
switch ( option ) {
// Some config options are merged together instead of entirely replaced.
case 'config':
case 'mappings': {
case 'mappings':
case 'lifecycleScripts': {
config[ option ] = Object.assign(
config[ option ],
toMerge[ option ]
Expand Down
Loading

0 comments on commit a73b80e

Please sign in to comment.