Skip to content

Commit

Permalink
Replaced afterSetup With Granular Scripts
Browse files Browse the repository at this point in the history
This removes `afterSetup` in favor of an `afterStart` and an
`afterClean` script. These will provide a more granular way
of handling things. We have also added a `beforeDestroy`
script that will help.
  • Loading branch information
ObliviousHarmony committed May 18, 2023
1 parent 2efad48 commit 08b0b0e
Show file tree
Hide file tree
Showing 17 changed files with 162 additions and 82 deletions.
9 changes: 9 additions & 0 deletions packages/env/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

## Unreleased

### Breaking Change

- 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 `beforeDestroy` 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.

### Enhancement

- Validate whether or not config options exist to prevent accidentally including ones that don't.
Expand Down
24 changes: 11 additions & 13 deletions packages/env/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,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 @@ -559,19 +560,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.

### After Setup

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:
## Lifecycle Scripts

- `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.
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.

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.
* `beforeDestroy`: Runs before `wp-env destroy` begins destroying anything.

## Examples

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

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

Expand Down
8 changes: 7 additions & 1 deletion packages/env/lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,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
3 changes: 1 addition & 2 deletions packages/env/lib/commands/clean.js
Original file line number Diff line number Diff line change
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 ) {
executeLifecycleScript( 'afterSetup', config, spinner );
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 All @@ -57,10 +57,14 @@ module.exports = async function destroy( { spinner, debug } ) {
return;
}

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

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 +74,7 @@ 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 );

spinner.text = 'Removed WordPress environment.';
};
9 changes: 4 additions & 5 deletions packages/env/lib/commands/start.js
Original file line number Diff line number Diff line change
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 ) {
executeLifecycleScript( 'afterSetup', config, spinner );
}

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

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

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

Expand Down
51 changes: 37 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,33 @@ 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.
for ( const env in process.env ) {
const match = env.match( /WP_ENV_LIFECYCLE_SCRIPT_([A-Za-z0-9_]+)/ );
if ( ! match ) {
continue;
}

const scriptValue = process.env[ env ];
checkString( 'environment variable', env, scriptValue );

// Convert the snake_case environment variables into a camelCase lifecycle script key.
const scriptKey = match[ 1 ]
.toLowerCase()
.replace( /(_[a-z])/g, ( group ) =>
group.toUpperCase().replace( '_', '' )
);
lifecycleScripts[ scriptKey ] = scriptValue;
}

return lifecycleScripts;
}
4 changes: 1 addition & 3 deletions packages/env/lib/config/load-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,7 @@ module.exports = async function loadConfig( configDirectoryPath ) {
configFilePath,
getConfigFilePath( configDirectoryPath, 'override' ),
] ),
lifecycleScripts: {
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
37 changes: 23 additions & 14 deletions packages/env/lib/config/parse-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ const mergeConfigs = require( './merge-configs' );
* The root configuration options.
*
* @typedef WPRootConfigOptions
* @property {number} port The port to use in the development environment.
* @property {number} testsPort The port to use in the tests environment.
* @property {string|null} afterSetup The command(s) to run after configuring WordPress on start and clean.
* @property {Object.<string, WPEnvironmentConfig>} env The environment-specific configuration options.
* @property {number} port The port to use in the development environment.
* @property {number} testsPort The port to use in the tests environment.
* @property {Object.<string, string|null>} lifecycleScripts The scripts to run at certain points in the command lifecycle.
* @property {Object.<string, WPEnvironmentConfig>} env The environment-specific configuration options.
*/

/**
Expand Down Expand Up @@ -223,7 +223,11 @@ async function getDefaultConfig(

// These configuration options are root-only and should not be present
// on environment-specific configuration objects.
afterSetup: null,
lifecycleScripts: {
afterStart: null,
afterClean: null,
afterDestroy: null,
},
env: {
development: {},
tests: {
Expand All @@ -250,6 +254,7 @@ function getEnvironmentVarOverrides( cacheDirectoryPath ) {
// Create a service config object so we can merge it with the others
// and override anything that the configuration options need to.
const overrideConfig = {
lifecycleScripts: overrides.lifecycleScripts,
env: {
development: {},
tests: {},
Expand Down Expand Up @@ -282,10 +287,6 @@ function getEnvironmentVarOverrides( cacheDirectoryPath ) {
overrideConfig.env.tests.phpVersion = overrides.phpVersion;
}

if ( overrides.afterSetup ) {
overrideConfig.afterSetup = overrides.afterSetup;
}

return overrideConfig;
}

Expand Down Expand Up @@ -333,12 +334,20 @@ async function parseRootConfig( configFile, rawConfig, options ) {
checkPort( configFile, `testsPort`, rawConfig.testsPort );
parsedConfig.testsPort = rawConfig.testsPort;
}
if ( rawConfig.afterSetup !== undefined ) {
// Support null as a valid input.
if ( rawConfig.afterSetup !== null ) {
checkString( configFile, 'afterSetup', rawConfig.afterSetup );
if ( rawConfig.lifecycleScripts ) {
parsedConfig.lifecycleScripts = {};

for ( const key in rawConfig.lifecycleScripts ) {
if ( rawConfig.lifecycleScripts[ key ] !== null ) {
checkString(
configFile,
key,
rawConfig.lifecycleScripts[ key ]
);
}
parsedConfig.lifecycleScripts[ key ] =
rawConfig.lifecycleScripts[ key ];
}
parsedConfig.afterSetup = rawConfig.afterSetup;
}

// Parse the environment-specific configs so they're accessible to the root.
Expand Down
6 changes: 3 additions & 3 deletions packages/env/lib/config/post-process-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ function mergeRootToEnvironments( config ) {
config.env.tests.port = config.testsPort;
delete config.testsPort;
}
if ( config.afterSetup !== undefined ) {
removedRootOptions.afterSetup = config.afterSetup;
delete config.afterSetup;
if ( config.lifecycleScripts !== undefined ) {
removedRootOptions.lifecycleScripts = config.lifecycleScripts;
delete config.lifecycleScripts;
}

// Merge the root config and the environment configs together so that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ exports[`Config Integration should load local and override configuration files 1
},
},
"lifecycleScripts": {
"afterSetup": null,
"afterEnd": "test",
"afterStart": null,
},
"name": "gutenberg",
"workDirectoryPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338",
Expand Down Expand Up @@ -131,7 +132,7 @@ exports[`Config Integration should load local configuration file 1`] = `
},
},
"lifecycleScripts": {
"afterSetup": "test",
"afterStart": "test",
},
"name": "gutenberg",
"workDirectoryPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338",
Expand Down Expand Up @@ -199,9 +200,7 @@ exports[`Config Integration should use default configuration 1`] = `
"themeSources": [],
},
},
"lifecycleScripts": {
"afterSetup": null,
},
"lifecycleScripts": {},
"name": "gutenberg",
"workDirectoryPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338",
}
Expand Down Expand Up @@ -271,7 +270,7 @@ exports[`Config Integration should use environment variables over local and over
},
},
"lifecycleScripts": {
"afterSetup": "test",
"afterStart": "test",
},
"name": "gutenberg",
"workDirectoryPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338",
Expand Down
Loading

0 comments on commit 08b0b0e

Please sign in to comment.