From c0a96e1d769ab5e7489f1aa0a9e17909c00dc8a3 Mon Sep 17 00:00:00 2001 From: Christopher Allford <6451942+ObliviousHarmony@users.noreply.github.com> Date: Fri, 28 Apr 2023 14:07:33 -0700 Subject: [PATCH 1/9] Added `afterSetup` Root Config Option --- packages/env/CHANGELOG.md | 5 ++++ .../get-config-from-environment-vars.js | 11 +++++++- packages/env/lib/config/load-config.js | 3 ++ packages/env/lib/config/parse-config.js | 15 ++++++++-- .../env/lib/config/post-process-config.js | 6 +++- .../__snapshots__/config-integration.js.snap | 4 +++ .../env/lib/config/test/config-integration.js | 4 +++ packages/env/lib/config/test/parse-config.js | 6 ++++ .../lib/config/test/post-process-config.js | 28 ++++++++++++++++++- .../env/lib/config/test/validate-config.js | 23 +++++++++++++++ packages/env/lib/config/validate-config.js | 20 +++++++++++++ 11 files changed, 120 insertions(+), 5 deletions(-) diff --git a/packages/env/CHANGELOG.md b/packages/env/CHANGELOG.md index aef44414c0db63..922a63747f744d 100644 --- a/packages/env/CHANGELOG.md +++ b/packages/env/CHANGELOG.md @@ -7,6 +7,11 @@ - Docker containers now run as the host user. This should resolve problems with permissions arising from different owners between the host, web container, and cli container. If you still encounter permissions issues, try running `npx wp-env destroy` so that the environment can be recreated with the correct permissions. +### New feature + +- Create an `afterSetup` option in `.wp-env.json` files for setting arbitrary commands to run after setting up WordPress when using `wp-env start` and `wp-env clean`. +- Add a `WP_ENV_AFTER_SETUP` environment variable to override the `afterSetup` option. + ### Bug fix - Ensure `wordpress`, `tests-wordpress`, `cli`, and `tests-cli` always build the correct Docker image. diff --git a/packages/env/lib/config/get-config-from-environment-vars.js b/packages/env/lib/config/get-config-from-environment-vars.js index 98eb7cf0b00812..1447d1a4de2719 100644 --- a/packages/env/lib/config/get-config-from-environment-vars.js +++ b/packages/env/lib/config/get-config-from-environment-vars.js @@ -6,7 +6,7 @@ const { parseSourceString, includeTestsPath, } = require( './parse-source-string' ); -const { checkPort, checkVersion } = require( './validate-config' ); +const { checkPort, checkVersion, checkString } = require( './validate-config' ); /** * @typedef {import('./parse-source-string').WPSource} WPSource @@ -53,6 +53,15 @@ 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; }; diff --git a/packages/env/lib/config/load-config.js b/packages/env/lib/config/load-config.js index d0f6130e221630..1459dbe5a4e62a 100644 --- a/packages/env/lib/config/load-config.js +++ b/packages/env/lib/config/load-config.js @@ -14,6 +14,7 @@ const { parseConfig, getConfigFilePath } = require( './parse-config' ); const postProcessConfig = require( './post-process-config' ); /** + * @typedef {import('./parse-config').WPRootConfig} WPRootConfig * @typedef {import('./parse-config').WPEnvironmentConfig} WPEnvironmentConfig */ @@ -26,6 +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.} env Specific config for different environments. * @property {boolean} debug True if debug mode is enabled. */ @@ -66,6 +68,7 @@ module.exports = async function loadConfig( configDirectoryPath ) { configFilePath, getConfigFilePath( configDirectoryPath, 'override' ), ] ), + afterSetup: config.afterSetup, env: config.env, }; }; diff --git a/packages/env/lib/config/parse-config.js b/packages/env/lib/config/parse-config.js index 18aad4e7b3540b..954ba29e72785b 100644 --- a/packages/env/lib/config/parse-config.js +++ b/packages/env/lib/config/parse-config.js @@ -14,6 +14,7 @@ const { } = require( './parse-source-string' ); const { ValidationError, + checkString, checkPort, checkStringArray, checkObjectWithValues, @@ -33,8 +34,9 @@ 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 {number} port The port to use in the development environment. + * @property {number} testsPort The port to use in the tests environment. + * @property {string} afterSetup The command(s) to run after configuring WordPress on start and clean. */ /** @@ -191,6 +193,7 @@ async function getDefaultConfig( WP_SITEURL: 'http://localhost', WP_HOME: 'http://localhost', }, + afterSetup: '', env: { development: {}, tests: { @@ -249,6 +252,10 @@ function getEnvironmentVarOverrides( cacheDirectoryPath ) { overrideConfig.env.tests.phpVersion = overrides.phpVersion; } + if ( overrides.afterSetup ) { + overrideConfig.afterSetup = overrides.afterSetup; + } + return overrideConfig; } @@ -293,6 +300,10 @@ async function parseRootConfig( configFile, rawConfig, options ) { checkPort( configFile, `testsPort`, rawConfig.testsPort ); parsedConfig.testsPort = rawConfig.testsPort; } + if ( rawConfig.afterSetup !== undefined ) { + checkString( configFile, 'afterSetup', rawConfig.afterSetup ); + parsedConfig.afterSetup = rawConfig.afterSetup; + } // Parse the environment-specific configs so they're accessible to the root. parsedConfig.env = {}; diff --git a/packages/env/lib/config/post-process-config.js b/packages/env/lib/config/post-process-config.js index 4855f8835e14d8..46723b9f3d8c3b 100644 --- a/packages/env/lib/config/post-process-config.js +++ b/packages/env/lib/config/post-process-config.js @@ -39,7 +39,7 @@ module.exports = function postProcessConfig( config ) { * @return {WPRootConfig} The config object with the root options merged together with the environment-specific options. */ function mergeRootToEnvironments( config ) { - // Some root-level options need to be merged early because they have a special + // Some root-level options need to be handled early because they have a special // cascade behavior that would break the normal merge. After merging we then // delete them to avoid that breakage and add them back before we return. const removedRootOptions = {}; @@ -59,6 +59,10 @@ function mergeRootToEnvironments( config ) { config.env.tests.port = config.testsPort; delete config.testsPort; } + if ( config.afterSetup !== undefined ) { + removedRootOptions.afterSetup = config.afterSetup; + delete config.afterSetup; + } // Merge the root config and the environment configs together so that // we can ignore the root config and have full environment configs. diff --git a/packages/env/lib/config/test/__snapshots__/config-integration.js.snap b/packages/env/lib/config/test/__snapshots__/config-integration.js.snap index da4e1f4d3a0bca..5af51042e5ecf2 100644 --- a/packages/env/lib/config/test/__snapshots__/config-integration.js.snap +++ b/packages/env/lib/config/test/__snapshots__/config-integration.js.snap @@ -2,6 +2,7 @@ exports[`Config Integration should load local and override configuration files 1`] = ` { + "afterSetup": "", "configDirectoryPath": "/test/gutenberg", "detectedLocalConfig": true, "dockerComposeConfigPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338/docker-compose.yml", @@ -68,6 +69,7 @@ exports[`Config Integration should load local and override configuration files 1 exports[`Config Integration should load local configuration file 1`] = ` { + "afterSetup": "test", "configDirectoryPath": "/test/gutenberg", "detectedLocalConfig": true, "dockerComposeConfigPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338/docker-compose.yml", @@ -134,6 +136,7 @@ exports[`Config Integration should load local configuration file 1`] = ` exports[`Config Integration should use default configuration 1`] = ` { + "afterSetup": "", "configDirectoryPath": "/test/gutenberg", "detectedLocalConfig": true, "dockerComposeConfigPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338/docker-compose.yml", @@ -200,6 +203,7 @@ exports[`Config Integration should use default configuration 1`] = ` exports[`Config Integration should use environment variables over local and override configuration files 1`] = ` { + "afterSetup": "test", "configDirectoryPath": "/test/gutenberg", "detectedLocalConfig": true, "dockerComposeConfigPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338/docker-compose.yml", diff --git a/packages/env/lib/config/test/config-integration.js b/packages/env/lib/config/test/config-integration.js index add5072c9eb09d..08c3277f06a45d 100644 --- a/packages/env/lib/config/test/config-integration.js +++ b/packages/env/lib/config/test/config-integration.js @@ -48,6 +48,7 @@ describe( 'Config Integration', () => { delete process.env.WP_ENV_HOME; delete process.env.WP_ENV_PORT; delete process.env.WP_ENV_TESTS_PORT; + delete process.env.WP_ENV_AFTER_SETUP; } ); it( 'should use default configuration', async () => { @@ -68,6 +69,7 @@ describe( 'Config Integration', () => { return JSON.stringify( { core: 'WordPress/WordPress#trunk', port: 123, + afterSetup: 'test', } ); } @@ -110,6 +112,7 @@ describe( 'Config Integration', () => { it( 'should use environment variables over local and override configuration files', async () => { process.env.WP_ENV_PORT = 12345; process.env.WP_ENV_TESTS_PORT = 61234; + process.env.WP_ENV_AFTER_SETUP = 'test'; readFile.mockImplementation( async ( fileName ) => { if ( fileName === '/test/gutenberg/.wp-env.json' ) { @@ -117,6 +120,7 @@ describe( 'Config Integration', () => { core: 'WordPress/WordPress#trunk', port: 123, testsPort: 456, + afterSetup: 'local', } ); } diff --git a/packages/env/lib/config/test/parse-config.js b/packages/env/lib/config/test/parse-config.js index eccb20a83c95ca..0d641b7859d24e 100644 --- a/packages/env/lib/config/test/parse-config.js +++ b/packages/env/lib/config/test/parse-config.js @@ -51,6 +51,7 @@ const DEFAULT_CONFIG = { WP_HOME: 'http://localhost', }, mappings: {}, + afterSetup: '', env: { development: {}, tests: { @@ -75,6 +76,7 @@ describe( 'parseConfig', () => { delete process.env.WP_ENV_TESTS_PORT; delete process.env.WP_ENV_CORE; delete process.env.WP_ENV_PHP_VERSION; + delete process.env.WP_ENV_AFTER_SETUP; } ); it( 'should return default config', async () => { @@ -139,6 +141,7 @@ describe( 'parseConfig', () => { return { core: 'WordPress/WordPress#Test', phpVersion: '1.0', + afterSetup: 'test', env: { development: { port: 1234, @@ -178,6 +181,7 @@ describe( 'parseConfig', () => { type: 'git', }, phpVersion: '2.0', + afterSetup: 'test', env: { development: { ...DEFAULT_CONFIG.env.development, @@ -266,6 +270,7 @@ describe( 'parseConfig', () => { process.env.WP_ENV_TESTS_PORT = 456; process.env.WP_ENV_CORE = 'WordPress/WordPress#test'; process.env.WP_ENV_PHP_VERSION = '3.0'; + process.env.WP_ENV_AFTER_SETUP = 'test after'; const parsed = await parseConfig( './', '/cache' ); @@ -283,6 +288,7 @@ describe( 'parseConfig', () => { type: 'git', }, phpVersion: '3.0', + afterSetup: 'test after', env: { development: { port: 123, diff --git a/packages/env/lib/config/test/post-process-config.js b/packages/env/lib/config/test/post-process-config.js index 0914b9acf1849d..db0a72d628f094 100644 --- a/packages/env/lib/config/test/post-process-config.js +++ b/packages/env/lib/config/test/post-process-config.js @@ -9,7 +9,7 @@ describe( 'postProcessConfig', () => { jest.clearAllMocks(); } ); - it( 'should merge root options into environment options', () => { + it( 'should merge relevant root options into environment options', () => { const processed = postProcessConfig( { port: 123, testsPort: 456, @@ -151,6 +151,32 @@ describe( 'postProcessConfig', () => { } ); } ); + it( 'should not merge some root options into environment options', () => { + const processed = postProcessConfig( { + port: 8888, + testsPort: 8889, + afterSetup: 'test', + env: { + development: {}, + tests: {}, + }, + } ); + + expect( processed ).toEqual( { + port: 8888, + testsPort: 8889, + afterSetup: 'test', + env: { + development: { + port: 8888, + }, + tests: { + port: 8889, + }, + }, + } ); + } ); + describe( 'appendPortToWPConfigs', () => { it( 'should add port to certain environment config options', () => { const processed = postProcessConfig( { diff --git a/packages/env/lib/config/test/validate-config.js b/packages/env/lib/config/test/validate-config.js index b612fbfb3549c1..4b67e05dc2aeae 100644 --- a/packages/env/lib/config/test/validate-config.js +++ b/packages/env/lib/config/test/validate-config.js @@ -3,6 +3,7 @@ */ const { ValidationError, + checkString, checkPort, checkStringArray, checkObjectWithValues, @@ -11,6 +12,28 @@ const { } = require( '../validate-config' ); describe( 'validate-config', () => { + describe( 'checkString', () => { + it( 'does nothing for undefined values', () => { + expect( () => + checkString( 'test.json', 'test', undefined ) + ).not.toThrow(); + } ); + + it( 'throws when not a string', () => { + expect( () => checkString( 'test.json', 'test', 1234 ) ).toThrow( + new ValidationError( + 'Invalid test.json: "test" must be a string.' + ) + ); + } ); + + it( 'passes for string', () => { + expect( () => + checkString( 'test.json', 'test', 'test' ) + ).not.toThrow(); + } ); + } ); + describe( 'checkPort', () => { it( 'does nothing for undefined values', () => { expect( () => diff --git a/packages/env/lib/config/validate-config.js b/packages/env/lib/config/validate-config.js index 7cadbeade4d514..3c3e7f3ee40fb4 100644 --- a/packages/env/lib/config/validate-config.js +++ b/packages/env/lib/config/validate-config.js @@ -10,6 +10,25 @@ */ class ValidationError extends Error {} +/** + * Validates that the value is a string. + * + * @param {string} configFile The configuration file we're validating. + * @param {string} configKey The configuration key we're validating. + * @param {number} value The value to check. + */ +function checkString( configFile, configKey, value ) { + if ( value === undefined ) { + return; + } + + if ( typeof value !== 'string' ) { + throw new ValidationError( + `Invalid ${ configFile }: "${ configKey }" must be a string.` + ); + } +} + /** * Validates the port and throws if it isn't valid. * @@ -153,6 +172,7 @@ function checkValidURL( configFile, configKey, url ) { module.exports = { ValidationError, + checkString, checkPort, checkStringArray, checkObjectWithValues, From a1b24ad0eb3b6e3505614257ccb8b2666a058063 Mon Sep 17 00:00:00 2001 From: Christopher Allford <6451942+ObliviousHarmony@users.noreply.github.com> Date: Fri, 28 Apr 2023 14:19:32 -0700 Subject: [PATCH 2/9] Fixed Inconsistent Catch Variable Naming --- packages/env/lib/config/test/parse-config.js | 4 ++-- packages/env/lib/config/test/parse-source-string.js | 4 ++-- packages/env/lib/config/test/read-raw-config-file.js | 4 ++-- packages/env/lib/config/validate-config.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/env/lib/config/test/parse-config.js b/packages/env/lib/config/test/parse-config.js index 0d641b7859d24e..1e9155acf5d4e6 100644 --- a/packages/env/lib/config/test/parse-config.js +++ b/packages/env/lib/config/test/parse-config.js @@ -330,8 +330,8 @@ describe( 'parseConfig', () => { expect.assertions( 1 ); try { await parseConfig( './', '/cache' ); - } catch ( e ) { - expect( e ).toEqual( + } catch ( error ) { + expect( error ).toEqual( new ValidationError( 'Could not find the latest WordPress version. There may be a network issue.' ) diff --git a/packages/env/lib/config/test/parse-source-string.js b/packages/env/lib/config/test/parse-source-string.js index a2a305833f4bf3..a0ccc6eb6d3f71 100644 --- a/packages/env/lib/config/test/parse-source-string.js +++ b/packages/env/lib/config/test/parse-source-string.js @@ -33,8 +33,8 @@ describe( 'parseSourceString', () => { expect.assertions( 1 ); try { parseSourceString( 'test://test', options ); - } catch ( e ) { - expect( e ).toEqual( + } catch ( error ) { + expect( error ).toEqual( new ValidationError( 'Invalid or unrecognized source: "test://test".' ) diff --git a/packages/env/lib/config/test/read-raw-config-file.js b/packages/env/lib/config/test/read-raw-config-file.js index b4607b135a559a..135bc4cd4d9c49 100644 --- a/packages/env/lib/config/test/read-raw-config-file.js +++ b/packages/env/lib/config/test/read-raw-config-file.js @@ -36,8 +36,8 @@ describe( 'readRawConfigFile', () => { try { await readRawConfigFile( '/.wp-env.json' ); - } catch ( e ) { - expect( e ).toEqual( + } catch ( error ) { + expect( error ).toEqual( new ValidationError( 'Could not read .wp-env.json: Test' ) ); } diff --git a/packages/env/lib/config/validate-config.js b/packages/env/lib/config/validate-config.js index 3c3e7f3ee40fb4..17c0fdf48a2438 100644 --- a/packages/env/lib/config/validate-config.js +++ b/packages/env/lib/config/validate-config.js @@ -163,7 +163,7 @@ function checkValidURL( configFile, configKey, url ) { try { new URL( url ); - } catch ( e ) { + } catch { throw new ValidationError( `Invalid ${ configFile }: "${ configKey }" must be a valid URL.` ); From c1b40cc83746949a09eb208e59ec70f6c8e383f9 Mon Sep 17 00:00:00 2001 From: Christopher Allford <6451942+ObliviousHarmony@users.noreply.github.com> Date: Fri, 28 Apr 2023 14:19:42 -0700 Subject: [PATCH 3/9] Fixed Broken Type Import --- packages/env/lib/config/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/env/lib/config/index.js b/packages/env/lib/config/index.js index 71f1ed5d51b303..b04d22ff3c30e9 100644 --- a/packages/env/lib/config/index.js +++ b/packages/env/lib/config/index.js @@ -6,7 +6,7 @@ const { ValidationError } = require( './validate-config' ); const dbEnv = require( './db-env' ); /** - * @typedef {import('./parse-config').WPConfig} WPConfig + * @typedef {import('./load-config').WPConfig} WPConfig * @typedef {import('./parse-config').WPRootConfig} WPRootConfig * @typedef {import('./parse-config').WPEnvironmentConfig} WPEnvironmentConfig * @typedef {import('./parse-source-string').WPSource} WPSource From dcf37dbcf012c8282ccd2f3b7ae30abfb4039e62 Mon Sep 17 00:00:00 2001 From: Christopher Allford <6451942+ObliviousHarmony@users.noreply.github.com> Date: Fri, 28 Apr 2023 14:21:48 -0700 Subject: [PATCH 4/9] Moved Test Files The `config` test files are under `lib/config` but the `lib` test files are under the root. This moves them to be consistent with the other test files. --- packages/env/bin/wp-env | 2 +- packages/env/{ => lib}/test/__snapshots__/md5.js.snap | 0 .../env/{ => lib}/test/build-docker-compose-config.js | 6 +++--- packages/env/{ => lib}/test/cache.js | 2 +- packages/env/{ => lib}/test/cli.js | 8 ++++---- packages/env/{ => lib}/test/md5.js | 2 +- packages/env/{ => lib}/test/parse-xdebug-mode.js | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) rename packages/env/{ => lib}/test/__snapshots__/md5.js.snap (100%) rename packages/env/{ => lib}/test/build-docker-compose-config.js (96%) rename packages/env/{ => lib}/test/cache.js (99%) rename packages/env/{ => lib}/test/cli.js (96%) rename packages/env/{ => lib}/test/md5.js (95%) rename packages/env/{ => lib}/test/parse-xdebug-mode.js (95%) diff --git a/packages/env/bin/wp-env b/packages/env/bin/wp-env index 7ce3e39103bcd1..817246b7960cfd 100755 --- a/packages/env/bin/wp-env +++ b/packages/env/bin/wp-env @@ -1,4 +1,4 @@ #!/usr/bin/env node 'use strict'; const command = process.argv.slice( 2 ); -require( '../lib/cli' )().parse( command.length ? command : [ '--help' ] ); +require( '../cli' )().parse( command.length ? command : [ '--help' ] ); diff --git a/packages/env/test/__snapshots__/md5.js.snap b/packages/env/lib/test/__snapshots__/md5.js.snap similarity index 100% rename from packages/env/test/__snapshots__/md5.js.snap rename to packages/env/lib/test/__snapshots__/md5.js.snap diff --git a/packages/env/test/build-docker-compose-config.js b/packages/env/lib/test/build-docker-compose-config.js similarity index 96% rename from packages/env/test/build-docker-compose-config.js rename to packages/env/lib/test/build-docker-compose-config.js index 444589d6ad637e..3093493e09a536 100644 --- a/packages/env/test/build-docker-compose-config.js +++ b/packages/env/lib/test/build-docker-compose-config.js @@ -1,8 +1,8 @@ /** * Internal dependencies */ -const buildDockerComposeConfig = require( '../lib/build-docker-compose-config' ); -const getHostUser = require( '../lib/get-host-user' ); +const buildDockerComposeConfig = require( '../build-docker-compose-config' ); +const getHostUser = require( '../get-host-user' ); // The basic config keys which build docker compose config requires. const CONFIG = { @@ -13,7 +13,7 @@ const CONFIG = { configDirectoryPath: '/path/to/config', }; -jest.mock( '../lib/get-host-user', () => jest.fn() ); +jest.mock( '../get-host-user', () => jest.fn() ); getHostUser.mockImplementation( () => { return { name: 'test', diff --git a/packages/env/test/cache.js b/packages/env/lib/test/cache.js similarity index 99% rename from packages/env/test/cache.js rename to packages/env/lib/test/cache.js index 3b7f17d11e1f56..f4512513c79286 100644 --- a/packages/env/test/cache.js +++ b/packages/env/lib/test/cache.js @@ -11,7 +11,7 @@ const { setCache, getCache, getCacheFile, -} = require( '../lib/cache' ); +} = require( '../cache' ); jest.mock( 'fs', () => ( { promises: { diff --git a/packages/env/test/cli.js b/packages/env/lib/test/cli.js similarity index 96% rename from packages/env/test/cli.js rename to packages/env/lib/test/cli.js index 9319750518095f..2cfaee214f512b 100644 --- a/packages/env/test/cli.js +++ b/packages/env/lib/test/cli.js @@ -2,8 +2,8 @@ /** * Internal dependencies */ -const cli = require( '../lib/cli' ); -const env = require( '../lib/env' ); +const cli = require( '../cli' ); +const env = require( '../env' ); /** * Mocked dependencies @@ -14,12 +14,12 @@ jest.mock( 'ora', () => () => ( { return { text: '', succeed: jest.fn(), fail: jest.fn() }; }, } ) ); -jest.mock( '../lib/env', () => ( { +jest.mock( '../env', () => ( { start: jest.fn( Promise.resolve.bind( Promise ) ), stop: jest.fn( Promise.resolve.bind( Promise ) ), clean: jest.fn( Promise.resolve.bind( Promise ) ), run: jest.fn( Promise.resolve.bind( Promise ) ), - ValidationError: jest.requireActual( '../lib/env' ).ValidationError, + ValidationError: jest.requireActual( '../env' ).ValidationError, } ) ); describe( 'env cli', () => { diff --git a/packages/env/test/md5.js b/packages/env/lib/test/md5.js similarity index 95% rename from packages/env/test/md5.js rename to packages/env/lib/test/md5.js index 2d07a589c1768e..9a7e0cc4990c0d 100644 --- a/packages/env/test/md5.js +++ b/packages/env/lib/test/md5.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -const md5 = require( '../lib/md5' ); +const md5 = require( '../md5' ); describe( 'md5', () => { it( 'creates a hash of a string', () => { diff --git a/packages/env/test/parse-xdebug-mode.js b/packages/env/lib/test/parse-xdebug-mode.js similarity index 95% rename from packages/env/test/parse-xdebug-mode.js rename to packages/env/lib/test/parse-xdebug-mode.js index 454611cd40d43e..9627323fc2a1fd 100644 --- a/packages/env/test/parse-xdebug-mode.js +++ b/packages/env/lib/test/parse-xdebug-mode.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -const parseXdebugMode = require( '../lib/parse-xdebug-mode' ); +const parseXdebugMode = require( '../parse-xdebug-mode' ); describe( 'parseXdebugMode', () => { it( 'throws an error if the passed value is neither a string nor undefined', () => { From 17e6298356778f032e73275022a41786e667d27f Mon Sep 17 00:00:00 2001 From: Christopher Allford <6451942+ObliviousHarmony@users.noreply.github.com> Date: Fri, 28 Apr 2023 14:25:58 -0700 Subject: [PATCH 5/9] Enforced Strict Mode --- packages/env/lib/cache.js | 1 + packages/env/lib/commands/clean.js | 1 + packages/env/lib/commands/destroy.js | 1 + packages/env/lib/commands/index.js | 1 + packages/env/lib/commands/install-path.js | 1 + packages/env/lib/commands/logs.js | 1 + packages/env/lib/commands/run.js | 1 + packages/env/lib/commands/start.js | 1 + packages/env/lib/commands/stop.js | 1 + packages/env/lib/config/add-or-replace-port.js | 1 + packages/env/lib/config/db-env.js | 1 + packages/env/lib/config/index.js | 1 + packages/env/lib/config/test/add-or-replace-port.js | 1 + packages/env/lib/config/test/post-process-config.js | 1 + packages/env/lib/config/test/validate-config.js | 1 + packages/env/lib/get-host-user.js | 1 + packages/env/lib/init-config.js | 1 + packages/env/lib/md5.js | 1 + packages/env/lib/parse-xdebug-mode.js | 1 + packages/env/lib/retry.js | 1 + packages/env/lib/test/build-docker-compose-config.js | 1 + packages/env/lib/test/cache.js | 1 + packages/env/lib/test/md5.js | 1 + packages/env/lib/test/parse-xdebug-mode.js | 1 + packages/env/lib/wordpress.js | 1 + 25 files changed, 25 insertions(+) diff --git a/packages/env/lib/cache.js b/packages/env/lib/cache.js index d6b532c559f49a..187a8a24d030ae 100644 --- a/packages/env/lib/cache.js +++ b/packages/env/lib/cache.js @@ -1,3 +1,4 @@ +'use strict'; /** * External dependencies */ diff --git a/packages/env/lib/commands/clean.js b/packages/env/lib/commands/clean.js index adde9d072d9eb0..a41f061c5ed234 100644 --- a/packages/env/lib/commands/clean.js +++ b/packages/env/lib/commands/clean.js @@ -1,3 +1,4 @@ +'use strict'; /** * External dependencies */ diff --git a/packages/env/lib/commands/destroy.js b/packages/env/lib/commands/destroy.js index 8abfed9e33d1b4..509f1e89c075a6 100644 --- a/packages/env/lib/commands/destroy.js +++ b/packages/env/lib/commands/destroy.js @@ -1,3 +1,4 @@ +'use strict'; /** * External dependencies */ diff --git a/packages/env/lib/commands/index.js b/packages/env/lib/commands/index.js index f040fe174687db..04b0a9a3678d1d 100644 --- a/packages/env/lib/commands/index.js +++ b/packages/env/lib/commands/index.js @@ -1,3 +1,4 @@ +'use strict'; /** * Internal dependencies */ diff --git a/packages/env/lib/commands/install-path.js b/packages/env/lib/commands/install-path.js index 4cc5358a65d023..a71a153339c47f 100644 --- a/packages/env/lib/commands/install-path.js +++ b/packages/env/lib/commands/install-path.js @@ -1,3 +1,4 @@ +'use strict'; /** * Internal dependencies */ diff --git a/packages/env/lib/commands/logs.js b/packages/env/lib/commands/logs.js index 704b89328d6b5e..3a749b20b3dab6 100644 --- a/packages/env/lib/commands/logs.js +++ b/packages/env/lib/commands/logs.js @@ -1,3 +1,4 @@ +'use strict'; /** * External dependencies */ diff --git a/packages/env/lib/commands/run.js b/packages/env/lib/commands/run.js index bed771322fd240..1e13f7a797c38a 100644 --- a/packages/env/lib/commands/run.js +++ b/packages/env/lib/commands/run.js @@ -1,3 +1,4 @@ +'use strict'; /** * External dependencies */ diff --git a/packages/env/lib/commands/start.js b/packages/env/lib/commands/start.js index d584809431f15b..689f00fa2ce6e2 100644 --- a/packages/env/lib/commands/start.js +++ b/packages/env/lib/commands/start.js @@ -1,3 +1,4 @@ +'use strict'; /** * External dependencies */ diff --git a/packages/env/lib/commands/stop.js b/packages/env/lib/commands/stop.js index 4f6688003ebb9d..3700c3f2aa5815 100644 --- a/packages/env/lib/commands/stop.js +++ b/packages/env/lib/commands/stop.js @@ -1,3 +1,4 @@ +'use strict'; /** * External dependencies */ diff --git a/packages/env/lib/config/add-or-replace-port.js b/packages/env/lib/config/add-or-replace-port.js index 8d891869238a31..605ede43644a1d 100644 --- a/packages/env/lib/config/add-or-replace-port.js +++ b/packages/env/lib/config/add-or-replace-port.js @@ -1,3 +1,4 @@ +'use strict'; /** * Internal dependencies */ diff --git a/packages/env/lib/config/db-env.js b/packages/env/lib/config/db-env.js index 95c0c23cf93d3e..aa73fc70d60ceb 100644 --- a/packages/env/lib/config/db-env.js +++ b/packages/env/lib/config/db-env.js @@ -1,3 +1,4 @@ +'use strict'; // Username and password used in all databases. const credentials = { WORDPRESS_DB_USER: 'root', diff --git a/packages/env/lib/config/index.js b/packages/env/lib/config/index.js index b04d22ff3c30e9..0fa88f44222752 100644 --- a/packages/env/lib/config/index.js +++ b/packages/env/lib/config/index.js @@ -1,3 +1,4 @@ +'use strict'; /** * Internal dependencies */ diff --git a/packages/env/lib/config/test/add-or-replace-port.js b/packages/env/lib/config/test/add-or-replace-port.js index cba4642c199629..1c9794f0d0125e 100644 --- a/packages/env/lib/config/test/add-or-replace-port.js +++ b/packages/env/lib/config/test/add-or-replace-port.js @@ -1,3 +1,4 @@ +'use strict'; /** * Internal dependencies */ diff --git a/packages/env/lib/config/test/post-process-config.js b/packages/env/lib/config/test/post-process-config.js index db0a72d628f094..8559728b969d1d 100644 --- a/packages/env/lib/config/test/post-process-config.js +++ b/packages/env/lib/config/test/post-process-config.js @@ -1,3 +1,4 @@ +'use strict'; /** * Internal dependencies */ diff --git a/packages/env/lib/config/test/validate-config.js b/packages/env/lib/config/test/validate-config.js index 4b67e05dc2aeae..4cee8fe44c2d9c 100644 --- a/packages/env/lib/config/test/validate-config.js +++ b/packages/env/lib/config/test/validate-config.js @@ -1,3 +1,4 @@ +'use strict'; /** * Internal dependencies */ diff --git a/packages/env/lib/get-host-user.js b/packages/env/lib/get-host-user.js index ab88b3f728d6b9..7c7f095de17157 100644 --- a/packages/env/lib/get-host-user.js +++ b/packages/env/lib/get-host-user.js @@ -1,3 +1,4 @@ +'use strict'; /** * External dependencies */ diff --git a/packages/env/lib/init-config.js b/packages/env/lib/init-config.js index ad46880042e432..659278eed2160b 100644 --- a/packages/env/lib/init-config.js +++ b/packages/env/lib/init-config.js @@ -1,3 +1,4 @@ +'use strict'; /** * External dependencies */ diff --git a/packages/env/lib/md5.js b/packages/env/lib/md5.js index 2fd6b7c6a6bf21..9f37b5a4ede626 100644 --- a/packages/env/lib/md5.js +++ b/packages/env/lib/md5.js @@ -1,3 +1,4 @@ +'use strict'; /** * External dependencies */ diff --git a/packages/env/lib/parse-xdebug-mode.js b/packages/env/lib/parse-xdebug-mode.js index 927b4f6e3f2334..0fb781d786fba4 100644 --- a/packages/env/lib/parse-xdebug-mode.js +++ b/packages/env/lib/parse-xdebug-mode.js @@ -1,3 +1,4 @@ +'use strict'; // See https://xdebug.org/docs/all_settings#mode const XDEBUG_MODES = [ 'develop', diff --git a/packages/env/lib/retry.js b/packages/env/lib/retry.js index 014e5b7bffe810..753a8123ead6a7 100644 --- a/packages/env/lib/retry.js +++ b/packages/env/lib/retry.js @@ -1,3 +1,4 @@ +'use strict'; /** * External dependencies */ diff --git a/packages/env/lib/test/build-docker-compose-config.js b/packages/env/lib/test/build-docker-compose-config.js index 3093493e09a536..50f5b2e42d0fac 100644 --- a/packages/env/lib/test/build-docker-compose-config.js +++ b/packages/env/lib/test/build-docker-compose-config.js @@ -1,3 +1,4 @@ +'use strict'; /** * Internal dependencies */ diff --git a/packages/env/lib/test/cache.js b/packages/env/lib/test/cache.js index f4512513c79286..63da7ec47d70bc 100644 --- a/packages/env/lib/test/cache.js +++ b/packages/env/lib/test/cache.js @@ -1,3 +1,4 @@ +'use strict'; /** * External dependencies */ diff --git a/packages/env/lib/test/md5.js b/packages/env/lib/test/md5.js index 9a7e0cc4990c0d..ad40f0040f16c8 100644 --- a/packages/env/lib/test/md5.js +++ b/packages/env/lib/test/md5.js @@ -1,3 +1,4 @@ +'use strict'; /** * Internal dependencies */ diff --git a/packages/env/lib/test/parse-xdebug-mode.js b/packages/env/lib/test/parse-xdebug-mode.js index 9627323fc2a1fd..c84ad16bb81c33 100644 --- a/packages/env/lib/test/parse-xdebug-mode.js +++ b/packages/env/lib/test/parse-xdebug-mode.js @@ -1,3 +1,4 @@ +'use strict'; /** * Internal dependencies */ diff --git a/packages/env/lib/wordpress.js b/packages/env/lib/wordpress.js index 7039e41f327fda..f238288498cf54 100644 --- a/packages/env/lib/wordpress.js +++ b/packages/env/lib/wordpress.js @@ -1,3 +1,4 @@ +'use strict'; /** * External dependencies */ From 14fe8f9bb00ec7b9fae050105fbfb5b1a9389cf4 Mon Sep 17 00:00:00 2001 From: Christopher Allford <6451942+ObliviousHarmony@users.noreply.github.com> Date: Fri, 28 Apr 2023 15:45:56 -0700 Subject: [PATCH 6/9] Execute `afterSetup` on `start` and `clean` --- packages/env/CHANGELOG.md | 2 + packages/env/bin/wp-env | 2 +- packages/env/lib/cli.js | 17 ++++- packages/env/lib/commands/clean.js | 20 ++++-- packages/env/lib/commands/start.js | 23 +++++-- packages/env/lib/env.js | 2 + packages/env/lib/execute-after-setup.js | 51 +++++++++++++++ packages/env/lib/test/cli.js | 18 +++--- packages/env/lib/test/execute-after-setup.js | 66 ++++++++++++++++++++ 9 files changed, 182 insertions(+), 19 deletions(-) create mode 100644 packages/env/lib/execute-after-setup.js create mode 100644 packages/env/lib/test/execute-after-setup.js diff --git a/packages/env/CHANGELOG.md b/packages/env/CHANGELOG.md index 922a63747f744d..767b382e1e096f 100644 --- a/packages/env/CHANGELOG.md +++ b/packages/env/CHANGELOG.md @@ -11,6 +11,8 @@ between the host, web container, and cli container. If you still encounter permi - Create an `afterSetup` option in `.wp-env.json` files for setting arbitrary commands to run after setting up WordPress when using `wp-env start` and `wp-env clean`. - Add a `WP_ENV_AFTER_SETUP` environment variable to override the `afterSetup` option. +- Execute the `afterSetup` command on `wp-env start` after the environment is set up. This can happen when your config changes, WordPress updates, or you pass the `--update` flag. +- Execute the `afterSetup` command on `wp-env clean`. ### Bug fix diff --git a/packages/env/bin/wp-env b/packages/env/bin/wp-env index 817246b7960cfd..7ce3e39103bcd1 100755 --- a/packages/env/bin/wp-env +++ b/packages/env/bin/wp-env @@ -1,4 +1,4 @@ #!/usr/bin/env node 'use strict'; const command = process.argv.slice( 2 ); -require( '../cli' )().parse( command.length ? command : [ '--help' ] ); +require( '../lib/cli' )().parse( command.length ? command : [ '--help' ] ); diff --git a/packages/env/lib/cli.js b/packages/env/lib/cli.js index 13bee46974bead..f8c9ee218df93a 100644 --- a/packages/env/lib/cli.js +++ b/packages/env/lib/cli.js @@ -39,8 +39,11 @@ const withSpinner = process.exit( 0 ); }, ( error ) => { - if ( error instanceof env.ValidationError ) { - // Error is a validation error. That means the user did something wrong. + if ( + error instanceof env.ValidationError || + error instanceof env.AfterSetupError + ) { + // Error is a configuration error. That means the user did something wrong. spinner.fail( error.message ); process.exit( 1 ); } else if ( @@ -124,6 +127,11 @@ module.exports = function cli() { coerce: parseXdebugMode, type: 'string', } ); + args.option( 'execute-scripts', { + type: 'boolean', + describe: 'Execute any configured lifecycle scripts.', + default: true, + } ); }, withSpinner( env.start ) ); @@ -145,6 +153,11 @@ module.exports = function cli() { choices: [ 'all', 'development', 'tests' ], default: 'tests', } ); + args.option( 'execute-scripts', { + type: 'boolean', + describe: 'Execute any configured lifecycle scripts.', + default: true, + } ); }, withSpinner( env.clean ) ); diff --git a/packages/env/lib/commands/clean.js b/packages/env/lib/commands/clean.js index a41f061c5ed234..d573d1906db986 100644 --- a/packages/env/lib/commands/clean.js +++ b/packages/env/lib/commands/clean.js @@ -9,6 +9,7 @@ const dockerCompose = require( 'docker-compose' ); */ const initConfig = require( '../init-config' ); const { configureWordPress, resetDatabase } = require( '../wordpress' ); +const { executeAfterSetup } = require( '../execute-after-setup' ); /** * @typedef {import('../wordpress').WPEnvironment} WPEnvironment @@ -19,11 +20,17 @@ const { configureWordPress, resetDatabase } = require( '../wordpress' ); * Wipes the development server's database, the tests server's database, or both. * * @param {Object} options - * @param {WPEnvironmentSelection} options.environment The environment to clean. Either 'development', 'tests', or 'all'. - * @param {Object} options.spinner A CLI spinner which indicates progress. - * @param {boolean} options.debug True if debug mode is enabled. + * @param {WPEnvironmentSelection} options.environment The environment to clean. Either 'development', 'tests', or 'all'. + * @param {Object} options.spinner A CLI spinner which indicates progress. + * @param {boolean} options.executeScripts Indicates whether or not lifecycle scripts should be executed. + * @param {boolean} options.debug True if debug mode is enabled. */ -module.exports = async function clean( { environment, spinner, debug } ) { +module.exports = async function clean( { + environment, + spinner, + executeScripts, + debug, +} ) { const config = await initConfig( { spinner, debug } ); const description = `${ environment } environment${ @@ -58,5 +65,10 @@ module.exports = async function clean( { environment, spinner, debug } ) { await Promise.all( tasks ); + // Execute any configured command that should run after the environment has finished being set up. + if ( executeScripts ) { + executeAfterSetup( config, spinner ); + } + spinner.text = `Cleaned ${ description }.`; }; diff --git a/packages/env/lib/commands/start.js b/packages/env/lib/commands/start.js index 689f00fa2ce6e2..94a62674275541 100644 --- a/packages/env/lib/commands/start.js +++ b/packages/env/lib/commands/start.js @@ -31,6 +31,7 @@ const { } = require( '../wordpress' ); const { didCacheChange, setCache } = require( '../cache' ); const md5 = require( '../md5' ); +const { executeAfterSetup } = require( '../execute-after-setup' ); /** * @typedef {import('../config').WPConfig} WPConfig @@ -41,12 +42,19 @@ const CONFIG_CACHE_KEY = 'config_checksum'; * Starts the development server. * * @param {Object} options - * @param {Object} options.spinner A CLI spinner which indicates progress. - * @param {boolean} options.debug True if debug mode is enabled. - * @param {boolean} options.update If true, update sources. - * @param {string} options.xdebug The Xdebug mode to set. + * @param {Object} options.spinner A CLI spinner which indicates progress. + * @param {boolean} options.update If true, update sources. + * @param {string} options.xdebug The Xdebug mode to set. + * @param {boolean} options.executeScripts Indicates whether or not lifecycle scripts should be executed. + * @param {boolean} options.debug True if debug mode is enabled. */ -module.exports = async function start( { spinner, debug, update, xdebug } ) { +module.exports = async function start( { + spinner, + update, + xdebug, + executeScripts, + debug, +} ) { spinner.text = 'Reading configuration.'; await checkForLegacyInstall( spinner ); @@ -195,6 +203,11 @@ module.exports = async function start( { spinner, debug, update, xdebug } ) { } ), ] ); + // Execute any configured command that should run after the environment has finished being set up. + if ( executeScripts ) { + executeAfterSetup( config, spinner ); + } + // Set the cache key once everything has been configured. await setCache( CONFIG_CACHE_KEY, configHash, { workDirectoryPath, diff --git a/packages/env/lib/env.js b/packages/env/lib/env.js index 3942a307109377..be093afe5a1067 100644 --- a/packages/env/lib/env.js +++ b/packages/env/lib/env.js @@ -3,9 +3,11 @@ * Internal dependencies */ const { ValidationError } = require( './config' ); +const { AfterSetupError } = require( './execute-after-setup' ); const commands = require( './commands' ); module.exports = { ...commands, ValidationError, + AfterSetupError, }; diff --git a/packages/env/lib/execute-after-setup.js b/packages/env/lib/execute-after-setup.js new file mode 100644 index 00000000000000..09b642d2fef566 --- /dev/null +++ b/packages/env/lib/execute-after-setup.js @@ -0,0 +1,51 @@ +'use strict'; +/** + * External dependencies + */ +const { execSync } = require( 'child_process' ); + +/** + * @typedef {import('./config').WPConfig} WPConfig + */ + +/** + * Error subtype which indicates that the afterSetup command failed. + */ +class AfterSetupError extends Error {} + +/** + * Executes any defined afterSetup command. + * + * @param {WPConfig} config The config object to use. + * @param {Object} spinner A CLI spinner which indciates progress. + */ +function executeAfterSetup( config, spinner ) { + if ( ! config.afterSetup ) { + return; + } + + spinner.text = 'Executing Script: afterSetup'; + + try { + let output = execSync( config.afterSetup, { + encoding: 'utf-8', + stdio: 'pipe', + env: process.env, + } ); + + // Remove any trailing whitespace for nicer output. + output = output.trimRight(); + + // We don't need to bother with any output if there isn't any. + if ( output ) { + spinner.info( `After Setup:\n${ output }` ); + } + } catch ( error ) { + throw new AfterSetupError( `After Setup:\n${ error.stderr }` ); + } +} + +module.exports = { + AfterSetupError, + executeAfterSetup, +}; diff --git a/packages/env/lib/test/cli.js b/packages/env/lib/test/cli.js index 2cfaee214f512b..336d2544936823 100644 --- a/packages/env/lib/test/cli.js +++ b/packages/env/lib/test/cli.js @@ -14,13 +14,17 @@ jest.mock( 'ora', () => () => ( { return { text: '', succeed: jest.fn(), fail: jest.fn() }; }, } ) ); -jest.mock( '../env', () => ( { - start: jest.fn( Promise.resolve.bind( Promise ) ), - stop: jest.fn( Promise.resolve.bind( Promise ) ), - clean: jest.fn( Promise.resolve.bind( Promise ) ), - run: jest.fn( Promise.resolve.bind( Promise ) ), - ValidationError: jest.requireActual( '../env' ).ValidationError, -} ) ); +jest.mock( '../env', () => { + const actual = jest.requireActual( '../env' ); + return { + start: jest.fn( Promise.resolve.bind( Promise ) ), + stop: jest.fn( Promise.resolve.bind( Promise ) ), + clean: jest.fn( Promise.resolve.bind( Promise ) ), + run: jest.fn( Promise.resolve.bind( Promise ) ), + ValidationError: actual.ValidationError, + AfterSetupError: actual.AfterSetupError, + }; +} ); describe( 'env cli', () => { beforeEach( jest.clearAllMocks ); diff --git a/packages/env/lib/test/execute-after-setup.js b/packages/env/lib/test/execute-after-setup.js new file mode 100644 index 00000000000000..c167c1470ec602 --- /dev/null +++ b/packages/env/lib/test/execute-after-setup.js @@ -0,0 +1,66 @@ +'use strict'; +/** + * External dependencies + */ +const { execSync } = require( 'child_process' ); + +/** + * Internal dependencies + */ +const { + AfterSetupError, + executeAfterSetup, +} = require( '../execute-after-setup' ); + +jest.mock( 'child_process', () => ( { + execSync: jest.fn(), +} ) ); + +describe( 'executeAfterSetup', () => { + const spinner = { + info: jest.fn(), + }; + + afterEach( () => { + jest.clearAllMocks(); + } ); + + it( 'should do nothing without afterSetup option', () => { + executeAfterSetup( { afterSetup: '' }, spinner ); + + expect( spinner.info ).not.toHaveBeenCalled(); + } ); + + it( 'should run afterSetup option and print output without extra whitespace', () => { + execSync.mockReturnValue( 'Test \n' ); + + executeAfterSetup( { afterSetup: 'Test Setup' }, spinner ); + + expect( execSync ).toHaveBeenCalled(); + expect( execSync.mock.calls[ 0 ][ 0 ] ).toEqual( 'Test Setup' ); + expect( spinner.info ).toHaveBeenCalledWith( 'After Setup:\nTest' ); + } ); + + it( 'should print nothing if afterSetup returns no output', () => { + execSync.mockReturnValue( '' ); + + executeAfterSetup( { afterSetup: 'Test Setup' }, spinner ); + + expect( execSync ).toHaveBeenCalled(); + expect( execSync.mock.calls[ 0 ][ 0 ] ).toEqual( 'Test Setup' ); + expect( spinner.info ).not.toHaveBeenCalled(); + } ); + + it( 'should throw AfterSetupError when process errors', () => { + execSync.mockImplementation( ( command ) => { + expect( command ).toEqual( 'Test Setup' ); + throw { stderr: 'Something bad happened.' }; + } ); + + expect( () => + executeAfterSetup( { afterSetup: 'Test Setup' }, spinner ) + ).toThrow( + new AfterSetupError( 'After Setup:\nSomething bad happened.' ) + ); + } ); +} ); From 6bb567af6edf72f8542afee8f351f3ed3e693b35 Mon Sep 17 00:00:00 2001 From: Christopher Allford <6451942+ObliviousHarmony@users.noreply.github.com> Date: Fri, 28 Apr 2023 16:00:15 -0700 Subject: [PATCH 7/9] Documented `afterSetup` Option --- packages/env/README.md | 87 +++++++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 27 deletions(-) diff --git a/packages/env/README.md b/packages/env/README.md index e6aea1cf18cf38..204b11e0cbaee9 100644 --- a/packages/env/README.md +++ b/packages/env/README.md @@ -253,23 +253,25 @@ The start command installs and initializes the WordPress environment, which incl ```sh wp-env start -Starts WordPress for development on port 8888 (override with WP_ENV_PORT) and -tests on port 8889 (override with WP_ENV_TESTS_PORT). The current working -directory must be a WordPress installation, a plugin, a theme, or contain a -.wp-env.json file. After first install, use the '--update' flag to download -updates to mapped sources and to re-apply WordPress configuration options. +Starts WordPress for development on port 8888 (​http://localhost:8888​) +(override with WP_ENV_PORT) and tests on port 8889 (​http://localhost:8889​) +(override with WP_ENV_TESTS_PORT). The current working directory must be a +WordPress installation, a plugin, a theme, or contain a .wp-env.json file. After +first install, use the '--update' flag to download updates to mapped sources and +to re-apply WordPress configuration options. Options: - --help Show help [boolean] - --version Show version number [boolean] - --debug Enable debug output. [boolean] [default: false] - --update Download source updates and apply WordPress configuration. + --debug Enable debug output. [boolean] [default: false] + --update Download source updates and apply WordPress configuration. [boolean] [default: false] - --xdebug Enables Xdebug. If not passed, Xdebug is turned off. If no modes - are set, uses "debug". You may set multiple Xdebug modes by passing - them in a comma-separated list: `--xdebug=develop,coverage`. See - https://xdebug.org/docs/all_settings#mode for information about - Xdebug modes. [string] + --xdebug Enables Xdebug. If not passed, Xdebug is turned off. If no + modes are set, uses "debug". You may set multiple Xdebug + modes by passing them in a comma-separated list: + `--xdebug=develop,coverage`. See + https://xdebug.org/docs/all_settings#mode for information + about Xdebug modes. [string] + --execute-scripts Execute any configured lifecycle scripts. + [boolean] [default: true] ``` ### `wp-env stop` @@ -278,6 +280,9 @@ Options: wp-env stop Stops running WordPress for development and tests and frees the ports. + +Options: + --debug Enable debug output. [boolean] [default: false] ``` ### `wp-env clean [environment]` @@ -290,6 +295,11 @@ Cleans the WordPress databases. Positionals: environment Which environments' databases to clean. [string] [choices: "all", "development", "tests"] [default: "tests"] + +Options: + --debug Enable debug output. [boolean] [default: false] + --execute-scripts Execute any configured lifecycle scripts. + [boolean] [default: true] ``` ### `wp-env run [container] [command]` @@ -327,8 +337,6 @@ Positionals: command The command to run. [array] [default: []] Options: - --help Show help [boolean] - --version Show version number [boolean] --debug Enable debug output. [boolean] [default: false] --env-cwd The command's working directory inside of the container. Paths without a leading slash are relative to the WordPress root. @@ -397,6 +405,9 @@ wp-env destroy 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] ``` ### `wp-env logs [environment]` @@ -411,8 +422,6 @@ Positionals: [string] [choices: "development", "tests", "all"] [default: "development"] Options: - --help Show help [boolean] - --version Show version number [boolean] --debug Enable debug output. [boolean] [default: false] --watch Watch for logs as they happen. [boolean] [default: true] ``` @@ -514,9 +523,23 @@ 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. -### Examples +## 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: -#### Latest stable WordPress + current directory as a plugin +- `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. + +## Examples + +### Latest stable WordPress + current directory as a plugin This is useful for plugin development. @@ -538,7 +561,7 @@ This is useful for plugin development when upstream Core changes need to be test } ``` -#### Local `wordpress-develop` + current directory as a plugin +### Local `wordpress-develop` + current directory as a plugin This is useful for working on plugins and WordPress Core at the same time. @@ -560,7 +583,7 @@ If you are running `wordpress-develop` in a dev mode (e.g. the watch command `de } ``` -#### A complete testing environment +### A complete testing environment This is useful for integration testing: that is, testing how old versions of WordPress and different combinations of plugins and themes impact each other. @@ -572,7 +595,7 @@ This is useful for integration testing: that is, testing how old versions of Wor } ``` -#### Add mu-plugins and other mapped directories +### Add mu-plugins and other mapped directories You can add mu-plugins via the mapping config. The mapping config also allows you to mount a directory to any location in the wordpress install, so you could even mount a subdirectory. Note here that theme-1, will not be activated. @@ -587,7 +610,7 @@ You can add mu-plugins via the mapping config. The mapping config also allows yo } ``` -#### Avoid activating plugins or themes on the instance +### Avoid activating plugins or themes on the instance Since all plugins in the `plugins` key are activated by default, you should use the `mappings` key to avoid this behavior. This might be helpful if you have a test plugin that should not be activated all the time. @@ -600,7 +623,7 @@ Since all plugins in the `plugins` key are activated by default, you should use } ``` -#### Map a plugin only in the tests environment +### Map a plugin only in the tests environment If you need a plugin active in one environment but not the other, you can use `env.` to set options specific to one environment. Here, we activate cwd and a test plugin on the tests instance. This plugin is not activated on any other instances. @@ -615,7 +638,7 @@ If you need a plugin active in one environment but not the other, you can use `e } ``` -#### Custom Port Numbers +### Custom Port Numbers You can tell `wp-env` to use a custom port number so that your instance does not conflict with other `wp-env` instances. @@ -631,7 +654,7 @@ You can tell `wp-env` to use a custom port number so that your instance does not } ``` -#### Specific PHP Version +### Specific PHP Version You can tell `wp-env` to use a specific PHP version for compatibility and testing. This can also be set via the environment variable `WP_ENV_PHP_VERSION`. @@ -642,6 +665,16 @@ You can tell `wp-env` to use a specific PHP version for compatibility and testin } ``` +### Node Lifecycle Script + +This is useful for performing some actions after setting up the environment, such as bootstrapping an E2E test environment. + +```json +{ + "afterSetup": "node tests/e2e/bin/setup-env.js" +} +``` + ## Contributing to this package This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. From 03b283dbeebf8352c0bc78900ae1edc208dad8dd Mon Sep 17 00:00:00 2001 From: Christopher Allford <6451942+ObliviousHarmony@users.noreply.github.com> Date: Mon, 1 May 2023 11:20:23 -0700 Subject: [PATCH 8/9] Changed `afterSetup` Default To `null` --- packages/env/lib/config/parse-config.js | 30 +++++++++------ .../__snapshots__/config-integration.js.snap | 4 +- packages/env/lib/config/test/parse-config.js | 2 +- .../env/lib/config/test/validate-config.js | 38 ------------------- packages/env/lib/config/validate-config.js | 24 ------------ packages/env/lib/test/execute-after-setup.js | 2 +- 6 files changed, 22 insertions(+), 78 deletions(-) diff --git a/packages/env/lib/config/parse-config.js b/packages/env/lib/config/parse-config.js index 954ba29e72785b..aa39d94ebfd9f8 100644 --- a/packages/env/lib/config/parse-config.js +++ b/packages/env/lib/config/parse-config.js @@ -34,9 +34,9 @@ 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} afterSetup The command(s) to run after configuring WordPress on start and clean. + * @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. */ /** @@ -49,7 +49,7 @@ const mergeConfigs = require( './merge-configs' ); * @property {number} port The port to use. * @property {Object} config Mapping of wp-config.php constants to their desired values. * @property {Object.} mappings Mapping of WordPress directories to local directories which should be mounted. - * @property {string} phpVersion Version of PHP to use in the environments, of the format 0.0. + * @property {string|null} phpVersion Version of PHP to use in the environments, of the format 0.0. */ /** @@ -193,7 +193,7 @@ async function getDefaultConfig( WP_SITEURL: 'http://localhost', WP_HOME: 'http://localhost', }, - afterSetup: '', + afterSetup: null, env: { development: {}, tests: { @@ -301,7 +301,10 @@ async function parseRootConfig( configFile, rawConfig, options ) { parsedConfig.testsPort = rawConfig.testsPort; } if ( rawConfig.afterSetup !== undefined ) { - checkString( configFile, 'afterSetup', rawConfig.afterSetup ); + // Support null as a valid input. + if ( rawConfig.afterSetup !== null ) { + checkString( configFile, 'afterSetup', rawConfig.afterSetup ); + } parsedConfig.afterSetup = rawConfig.afterSetup; } @@ -353,11 +356,14 @@ async function parseEnvironmentConfig( } if ( config.phpVersion !== undefined ) { - checkVersion( - configFile, - `${ environmentPrefix }phpVersion`, - config.phpVersion - ); + // Support null as a valid input. + if ( config.phpVersion !== null ) { + checkVersion( + configFile, + `${ environmentPrefix }phpVersion`, + config.phpVersion + ); + } parsedConfig.phpVersion = config.phpVersion; } @@ -407,7 +413,7 @@ async function parseEnvironmentConfig( checkValidURL( configFile, `${ environmentPrefix }config.${ key }`, - parsedConfig[ key ] + parsedConfig.config[ key ] ); break; } diff --git a/packages/env/lib/config/test/__snapshots__/config-integration.js.snap b/packages/env/lib/config/test/__snapshots__/config-integration.js.snap index 5af51042e5ecf2..914b0ec7cc0a45 100644 --- a/packages/env/lib/config/test/__snapshots__/config-integration.js.snap +++ b/packages/env/lib/config/test/__snapshots__/config-integration.js.snap @@ -2,7 +2,7 @@ exports[`Config Integration should load local and override configuration files 1`] = ` { - "afterSetup": "", + "afterSetup": null, "configDirectoryPath": "/test/gutenberg", "detectedLocalConfig": true, "dockerComposeConfigPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338/docker-compose.yml", @@ -136,7 +136,7 @@ exports[`Config Integration should load local configuration file 1`] = ` exports[`Config Integration should use default configuration 1`] = ` { - "afterSetup": "", + "afterSetup": null, "configDirectoryPath": "/test/gutenberg", "detectedLocalConfig": true, "dockerComposeConfigPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338/docker-compose.yml", diff --git a/packages/env/lib/config/test/parse-config.js b/packages/env/lib/config/test/parse-config.js index 1e9155acf5d4e6..ce5e28809ecd23 100644 --- a/packages/env/lib/config/test/parse-config.js +++ b/packages/env/lib/config/test/parse-config.js @@ -51,7 +51,7 @@ const DEFAULT_CONFIG = { WP_HOME: 'http://localhost', }, mappings: {}, - afterSetup: '', + afterSetup: null, env: { development: {}, tests: { diff --git a/packages/env/lib/config/test/validate-config.js b/packages/env/lib/config/test/validate-config.js index 4cee8fe44c2d9c..adcae2b90e2418 100644 --- a/packages/env/lib/config/test/validate-config.js +++ b/packages/env/lib/config/test/validate-config.js @@ -14,12 +14,6 @@ const { describe( 'validate-config', () => { describe( 'checkString', () => { - it( 'does nothing for undefined values', () => { - expect( () => - checkString( 'test.json', 'test', undefined ) - ).not.toThrow(); - } ); - it( 'throws when not a string', () => { expect( () => checkString( 'test.json', 'test', 1234 ) ).toThrow( new ValidationError( @@ -36,12 +30,6 @@ describe( 'validate-config', () => { } ); describe( 'checkPort', () => { - it( 'does nothing for undefined values', () => { - expect( () => - checkPort( 'test.json', 'test', undefined ) - ).not.toThrow(); - } ); - it( 'throws when not a number', () => { expect( () => checkPort( 'test.json', 'test', 'test' ) ).toThrow( new ValidationError( @@ -80,12 +68,6 @@ describe( 'validate-config', () => { } ); describe( 'checkStringArray', () => { - it( 'does nothing for undefined values', () => { - expect( () => - checkStringArray( 'test.json', 'test', undefined ) - ).not.toThrow(); - } ); - it( 'throws when not an array', () => { expect( () => checkStringArray( 'test.json', 'test', 'test' ) @@ -133,14 +115,6 @@ describe( 'validate-config', () => { } ); describe( 'checkObjectWithValues', () => { - it( 'does nothing for undefined values', () => { - expect( () => - checkObjectWithValues( 'test.json', 'test', undefined, [ - 'string', - ] ) - ).not.toThrow(); - } ); - it( 'throws when not an object', () => { expect( () => checkObjectWithValues( 'test.json', 'test', 'test', [] ) @@ -240,12 +214,6 @@ describe( 'validate-config', () => { } ); describe( 'checkVersion', () => { - it( 'does nothing for undefined values', () => { - expect( () => - checkVersion( 'test.json', 'test', undefined ) - ).not.toThrow(); - } ); - it( 'throws for invalid input', () => { expect( () => checkVersion( 'test.json', 'test', 'test' ) ).toThrow( new ValidationError( @@ -280,12 +248,6 @@ describe( 'validate-config', () => { } ); describe( 'checkValidURL', () => { - it( 'does nothing for undefined values', () => { - expect( () => - checkValidURL( 'test.json', 'test', undefined ) - ).not.toThrow(); - } ); - it( 'throws for invaid URLs', () => { expect( () => checkValidURL( 'test.json', 'test', 'localhost' ) diff --git a/packages/env/lib/config/validate-config.js b/packages/env/lib/config/validate-config.js index 17c0fdf48a2438..02a673eea3cb8d 100644 --- a/packages/env/lib/config/validate-config.js +++ b/packages/env/lib/config/validate-config.js @@ -18,10 +18,6 @@ class ValidationError extends Error {} * @param {number} value The value to check. */ function checkString( configFile, configKey, value ) { - if ( value === undefined ) { - return; - } - if ( typeof value !== 'string' ) { throw new ValidationError( `Invalid ${ configFile }: "${ configKey }" must be a string.` @@ -37,10 +33,6 @@ function checkString( configFile, configKey, value ) { * @param {number} port The port to check. */ function checkPort( configFile, configKey, port ) { - if ( port === undefined ) { - return; - } - if ( ! Number.isInteger( port ) ) { throw new ValidationError( `Invalid ${ configFile }: "${ configKey }" must be an integer.` @@ -64,10 +56,6 @@ function checkPort( configFile, configKey, port ) { * @param {string[]} array The array that we're checking. */ function checkStringArray( configFile, configKey, array ) { - if ( array === undefined ) { - return; - } - if ( ! Array.isArray( array ) ) { throw new ValidationError( `Invalid ${ configFile }: "${ configKey }" must be an array.` @@ -90,10 +78,6 @@ function checkStringArray( configFile, configKey, array ) { * @param {string[]} allowTypes The types that are allowed. */ function checkObjectWithValues( configFile, configKey, obj, allowTypes ) { - if ( obj === undefined ) { - return; - } - if ( allowTypes === undefined ) { allowTypes = []; } @@ -132,10 +116,6 @@ function checkObjectWithValues( configFile, configKey, obj, allowTypes ) { * @param {string} version The version that we're checking. */ function checkVersion( configFile, configKey, version ) { - if ( version === undefined || version === null ) { - return; - } - if ( typeof version !== 'string' ) { throw new ValidationError( `Invalid ${ configFile }: "${ configKey }" must be a string.` @@ -157,10 +137,6 @@ function checkVersion( configFile, configKey, version ) { * @param {string} url The URL that we're checking. */ function checkValidURL( configFile, configKey, url ) { - if ( url === undefined ) { - return; - } - try { new URL( url ); } catch { diff --git a/packages/env/lib/test/execute-after-setup.js b/packages/env/lib/test/execute-after-setup.js index c167c1470ec602..4ea0e8fd9bfddd 100644 --- a/packages/env/lib/test/execute-after-setup.js +++ b/packages/env/lib/test/execute-after-setup.js @@ -26,7 +26,7 @@ describe( 'executeAfterSetup', () => { } ); it( 'should do nothing without afterSetup option', () => { - executeAfterSetup( { afterSetup: '' }, spinner ); + executeAfterSetup( { afterSetup: null }, spinner ); expect( spinner.info ).not.toHaveBeenCalled(); } ); From 899e3a2a5aa515bec3e1978aa9fcb333cc547900 Mon Sep 17 00:00:00 2001 From: Christopher Allford <6451942+ObliviousHarmony@users.noreply.github.com> Date: Mon, 1 May 2023 11:24:04 -0700 Subject: [PATCH 9/9] Changed `--execute-scripts` to `--scripts` --- packages/env/README.md | 23 ++++++++++------------- packages/env/lib/cli.js | 4 ++-- packages/env/lib/commands/clean.js | 12 ++++++------ packages/env/lib/commands/start.js | 14 +++++++------- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/packages/env/README.md b/packages/env/README.md index 204b11e0cbaee9..1b9e292171da11 100644 --- a/packages/env/README.md +++ b/packages/env/README.md @@ -261,17 +261,15 @@ first install, use the '--update' flag to download updates to mapped sources and to re-apply WordPress configuration options. Options: - --debug Enable debug output. [boolean] [default: false] - --update Download source updates and apply WordPress configuration. + --debug Enable debug output. [boolean] [default: false] + --update Download source updates and apply WordPress configuration. [boolean] [default: false] - --xdebug Enables Xdebug. If not passed, Xdebug is turned off. If no - modes are set, uses "debug". You may set multiple Xdebug - modes by passing them in a comma-separated list: - `--xdebug=develop,coverage`. See - https://xdebug.org/docs/all_settings#mode for information - about Xdebug modes. [string] - --execute-scripts Execute any configured lifecycle scripts. - [boolean] [default: true] + --xdebug Enables Xdebug. If not passed, Xdebug is turned off. If no modes + are set, uses "debug". You may set multiple Xdebug modes by passing + them in a comma-separated list: `--xdebug=develop,coverage`. See + https://xdebug.org/docs/all_settings#mode for information about + Xdebug modes. [string] + --scripts Execute any configured lifecycle scripts. [boolean] [default: true] ``` ### `wp-env stop` @@ -297,9 +295,8 @@ Positionals: [string] [choices: "all", "development", "tests"] [default: "tests"] Options: - --debug Enable debug output. [boolean] [default: false] - --execute-scripts Execute any configured lifecycle scripts. - [boolean] [default: true] + --debug Enable debug output. [boolean] [default: false] + --scripts Execute any configured lifecycle scripts. [boolean] [default: true] ``` ### `wp-env run [container] [command]` diff --git a/packages/env/lib/cli.js b/packages/env/lib/cli.js index f8c9ee218df93a..115018a329c024 100644 --- a/packages/env/lib/cli.js +++ b/packages/env/lib/cli.js @@ -127,7 +127,7 @@ module.exports = function cli() { coerce: parseXdebugMode, type: 'string', } ); - args.option( 'execute-scripts', { + args.option( 'scripts', { type: 'boolean', describe: 'Execute any configured lifecycle scripts.', default: true, @@ -153,7 +153,7 @@ module.exports = function cli() { choices: [ 'all', 'development', 'tests' ], default: 'tests', } ); - args.option( 'execute-scripts', { + args.option( 'scripts', { type: 'boolean', describe: 'Execute any configured lifecycle scripts.', default: true, diff --git a/packages/env/lib/commands/clean.js b/packages/env/lib/commands/clean.js index d573d1906db986..20b7b1550c704e 100644 --- a/packages/env/lib/commands/clean.js +++ b/packages/env/lib/commands/clean.js @@ -20,15 +20,15 @@ const { executeAfterSetup } = require( '../execute-after-setup' ); * Wipes the development server's database, the tests server's database, or both. * * @param {Object} options - * @param {WPEnvironmentSelection} options.environment The environment to clean. Either 'development', 'tests', or 'all'. - * @param {Object} options.spinner A CLI spinner which indicates progress. - * @param {boolean} options.executeScripts Indicates whether or not lifecycle scripts should be executed. - * @param {boolean} options.debug True if debug mode is enabled. + * @param {WPEnvironmentSelection} options.environment The environment to clean. Either 'development', 'tests', or 'all'. + * @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 clean( { environment, spinner, - executeScripts, + scripts, debug, } ) { const config = await initConfig( { spinner, debug } ); @@ -66,7 +66,7 @@ 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 ( executeScripts ) { + if ( scripts ) { executeAfterSetup( config, spinner ); } diff --git a/packages/env/lib/commands/start.js b/packages/env/lib/commands/start.js index 94a62674275541..474d8fefbdaf05 100644 --- a/packages/env/lib/commands/start.js +++ b/packages/env/lib/commands/start.js @@ -42,17 +42,17 @@ const CONFIG_CACHE_KEY = 'config_checksum'; * Starts the development server. * * @param {Object} options - * @param {Object} options.spinner A CLI spinner which indicates progress. - * @param {boolean} options.update If true, update sources. - * @param {string} options.xdebug The Xdebug mode to set. - * @param {boolean} options.executeScripts Indicates whether or not lifecycle scripts should be executed. - * @param {boolean} options.debug True if debug mode is enabled. + * @param {Object} options.spinner A CLI spinner which indicates progress. + * @param {boolean} options.update If true, update sources. + * @param {string} options.xdebug The Xdebug mode to set. + * @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 start( { spinner, update, xdebug, - executeScripts, + scripts, debug, } ) { spinner.text = 'Reading configuration.'; @@ -204,7 +204,7 @@ module.exports = async function start( { ] ); // Execute any configured command that should run after the environment has finished being set up. - if ( executeScripts ) { + if ( scripts ) { executeAfterSetup( config, spinner ); }