@wordpress/env 10.38.1-next.v.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +158 -75
  2. package/lib/cli.js +85 -24
  3. package/lib/commands/clean.js +16 -38
  4. package/lib/commands/cleanup.js +79 -0
  5. package/lib/commands/destroy.js +33 -41
  6. package/lib/commands/index.js +6 -2
  7. package/lib/commands/logs.js +20 -62
  8. package/lib/commands/reset.js +46 -0
  9. package/lib/commands/run.js +16 -117
  10. package/lib/commands/start.js +78 -243
  11. package/lib/commands/status.js +160 -0
  12. package/lib/commands/stop.js +17 -19
  13. package/lib/config/get-config-from-environment-vars.js +2 -5
  14. package/lib/config/load-config.js +35 -5
  15. package/lib/config/parse-config.js +53 -21
  16. package/lib/config/post-process-config.js +38 -16
  17. package/lib/config/test/__snapshots__/config-integration.js.snap +16 -0
  18. package/lib/config/test/parse-config.js +33 -0
  19. package/lib/config/test/post-process-config.js +52 -0
  20. package/lib/download-sources.js +9 -53
  21. package/lib/{build-docker-compose-config.js → runtime/docker/build-docker-compose-config.js} +167 -128
  22. package/lib/runtime/docker/download-sources.js +63 -0
  23. package/lib/{download-wp-phpunit.js → runtime/docker/download-wp-phpunit.js} +4 -1
  24. package/lib/runtime/docker/index.js +863 -0
  25. package/lib/{init-config.js → runtime/docker/init-config.js} +22 -14
  26. package/lib/runtime/docker/wordpress.js +309 -0
  27. package/lib/runtime/errors.js +28 -0
  28. package/lib/runtime/index.js +91 -0
  29. package/lib/runtime/playground/blueprint-builder.js +158 -0
  30. package/lib/runtime/playground/index.js +530 -0
  31. package/lib/test/build-docker-compose-config.js +150 -3
  32. package/lib/test/cli.js +49 -13
  33. package/lib/wordpress.js +1 -297
  34. package/package.json +6 -3
  35. package/lib/commands/install-path.js +0 -21
  36. /package/lib/{get-host-user.js → runtime/docker/get-host-user.js} +0 -0
  37. /package/lib/{validate-run-container.js → runtime/docker/validate-run-container.js} +0 -0
@@ -2,53 +2,34 @@
2
2
  /**
3
3
  * External dependencies
4
4
  */
5
- const { v2: dockerCompose } = require( 'docker-compose' );
6
- const util = require( 'util' );
7
- const path = require( 'path' );
8
5
  const fs = require( 'fs' ).promises;
6
+ const path = require( 'path' );
9
7
  const { confirm } = require( '@inquirer/prompts' );
10
-
11
- /**
12
- * Promisified dependencies
13
- */
14
- const sleep = util.promisify( setTimeout );
15
8
  const { rimraf } = require( 'rimraf' );
16
- const exec = util.promisify( require( 'child_process' ).exec );
17
9
 
18
10
  /**
19
11
  * Internal dependencies
20
12
  */
21
- const retry = require( '../retry' );
22
- const stop = require( './stop' );
23
- const initConfig = require( '../init-config' );
24
- const downloadSources = require( '../download-sources' );
25
- const downloadWPPHPUnit = require( '../download-wp-phpunit' );
26
- const {
27
- checkDatabaseConnection,
28
- configureWordPress,
29
- setupWordPressDirectories,
30
- readWordPressVersion,
31
- canAccessWPORG,
32
- } = require( '../wordpress' );
33
- const { didCacheChange, setCache } = require( '../cache' );
34
- const md5 = require( '../md5' );
13
+ const { loadConfig } = require( '../config' );
35
14
  const { executeLifecycleScript } = require( '../execute-lifecycle-script' );
15
+ const { getRuntime, getSavedRuntime, saveRuntime } = require( '../runtime' );
36
16
 
37
17
  /**
38
18
  * @typedef {import('../config').WPConfig} WPConfig
39
19
  */
40
- const CONFIG_CACHE_KEY = 'config_checksum';
41
20
 
42
21
  /**
43
22
  * Starts the development server.
44
23
  *
45
- * @param {Object} options
46
- * @param {Object} options.spinner A CLI spinner which indicates progress.
47
- * @param {boolean} options.update If true, update sources.
48
- * @param {string} options.xdebug The Xdebug mode to set.
49
- * @param {string} options.spx The SPX mode to set.
50
- * @param {boolean} options.scripts Indicates whether or not lifecycle scripts should be executed.
51
- * @param {boolean} options.debug True if debug mode is enabled.
24
+ * @param {Object} options
25
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
26
+ * @param {boolean} options.update If true, update sources.
27
+ * @param {string} options.xdebug The Xdebug mode to set.
28
+ * @param {string} options.spx The SPX mode to set.
29
+ * @param {boolean} options.scripts Indicates whether or not lifecycle scripts should be executed.
30
+ * @param {boolean} options.debug True if debug mode is enabled.
31
+ * @param {string} options.runtime The runtime to use ('docker' or 'playground').
32
+ * @param {string|null} options.config Path to a custom .wp-env.json configuration file.
52
33
  */
53
34
  module.exports = async function start( {
54
35
  spinner,
@@ -57,250 +38,104 @@ module.exports = async function start( {
57
38
  spx,
58
39
  scripts,
59
40
  debug,
41
+ runtime: runtimeName = 'docker',
42
+ config: customConfigPath,
60
43
  } ) {
61
44
  spinner.text = 'Reading configuration.';
62
- await checkForLegacyInstall( spinner );
63
45
 
64
- const config = await initConfig( {
65
- spinner,
66
- debug,
67
- xdebug,
68
- spx,
69
- writeChanges: true,
70
- } );
46
+ const runtime = getRuntime( runtimeName );
71
47
 
72
- if ( ! config.detectedLocalConfig ) {
73
- const { configDirectoryPath } = config;
74
- spinner.warn(
75
- `Warning: could not find a .wp-env.json configuration file and could not determine if '${ configDirectoryPath }' is a WordPress installation, a plugin, or a theme.`
76
- );
77
- spinner.start();
78
- }
79
-
80
- // Check if the hash of the config has changed. If so, run configuration.
81
- const configHash = md5( config );
82
- const { workDirectoryPath, dockerComposeConfigPath } = config;
83
- const shouldConfigureWp =
84
- ( update ||
85
- ( await didCacheChange( CONFIG_CACHE_KEY, configHash, {
86
- workDirectoryPath,
87
- } ) ) ) &&
88
- // Don't reconfigure everything when we can't connect to the internet because
89
- // the majority of update tasks involve connecting to the internet. (Such
90
- // as downloading sources and pulling docker images.)
91
- ( await canAccessWPORG() );
92
-
93
- const dockerComposeConfig = {
94
- config: dockerComposeConfigPath,
95
- log: config.debug,
96
- };
97
-
98
- if ( ! ( await canAccessWPORG() ) ) {
99
- spinner.info( 'wp-env is offline' );
48
+ // Check for legacy Docker installs (Docker-specific UI concern)
49
+ if ( runtimeName === 'docker' ) {
50
+ await checkForLegacyInstall( spinner );
100
51
  }
101
52
 
102
- /**
103
- * If the Docker image is already running and the `wp-env` files have been
104
- * deleted, the start command will not complete successfully. Stopping
105
- * the container before continuing allows the docker entrypoint script,
106
- * which restores the files, to run again when we start the containers.
107
- *
108
- * Additionally, this serves as a way to restart the container entirely
109
- * should the need arise.
110
- *
111
- * @see https://github.com/WordPress/gutenberg/pull/20253#issuecomment-587228440
112
- */
113
- if ( shouldConfigureWp ) {
114
- await stop( { spinner, debug } );
115
- // Update the images before starting the services again.
116
- spinner.text = 'Updating docker images.';
117
-
118
- const directoryHash = path.basename( workDirectoryPath );
119
-
120
- // Note: when the base docker image is updated, we want that operation to
121
- // also update WordPress. Since we store wordpress/tests-wordpress files
122
- // as docker volumes, simply updating the image will not change those
123
- // files. Thus, we need to remove those volumes in order for the files
124
- // to be updated when pulling the new images.
125
- const volumesToRemove = `${ directoryHash }_wordpress ${ directoryHash }_tests-wordpress`;
53
+ const config = await loadConfig( path.resolve( '.' ), customConfigPath );
54
+ config.debug = debug;
126
55
 
56
+ // Check if switching runtimes and prompt user to destroy old environment first.
57
+ const savedRuntime = await getSavedRuntime( config.workDirectoryPath );
58
+ if ( savedRuntime && savedRuntime !== runtimeName ) {
59
+ spinner.stop();
60
+ let shouldDestroy = false;
127
61
  try {
128
- if ( config.debug ) {
129
- spinner.text = `Removing the WordPress volumes: ${ volumesToRemove }`;
62
+ shouldDestroy = await confirm( {
63
+ message: `Environment was previously started with '${ savedRuntime }' runtime. Destroy it and start with '${ runtimeName }'?`,
64
+ default: true,
65
+ } );
66
+ } catch ( error ) {
67
+ if ( error.name === 'ExitPromptError' ) {
68
+ console.log( 'Cancelled.' );
69
+ process.exit( 1 );
130
70
  }
131
- await exec( `docker volume rm ${ volumesToRemove }` );
132
- } catch {
133
- // Note: we do not care about this error condition because it will
134
- // mostly happen when the volume already exists. This error would not
135
- // stop wp-env from working correctly.
71
+ throw error;
136
72
  }
137
73
 
138
- await dockerCompose.pullAll( dockerComposeConfig );
139
- spinner.text = 'Downloading sources.';
140
- }
141
-
142
- await Promise.all( [
143
- dockerCompose.upOne( 'mysql', {
144
- ...dockerComposeConfig,
145
- commandOptions: shouldConfigureWp
146
- ? [ '--build', '--force-recreate' ]
147
- : [],
148
- } ),
149
- shouldConfigureWp && downloadSources( config, spinner ),
150
- ] );
151
-
152
- if ( shouldConfigureWp ) {
153
- spinner.text = 'Setting up WordPress directories';
154
-
155
- await setupWordPressDirectories( config );
156
-
157
- // Use the WordPress versions to download the PHPUnit suite.
158
- const wpVersions = await Promise.all( [
159
- readWordPressVersion(
160
- config.env.development.coreSource,
161
- spinner,
162
- debug
163
- ),
164
- readWordPressVersion( config.env.tests.coreSource, spinner, debug ),
165
- ] );
166
- await downloadWPPHPUnit(
167
- config,
168
- { development: wpVersions[ 0 ], tests: wpVersions[ 1 ] },
169
- spinner,
170
- debug
171
- );
172
- }
173
-
174
- spinner.text = 'Starting WordPress.';
175
-
176
- await dockerCompose.upMany(
177
- [ 'wordpress', 'tests-wordpress', 'cli', 'tests-cli' ],
178
- {
179
- ...dockerComposeConfig,
180
- commandOptions: shouldConfigureWp
181
- ? [ '--build', '--force-recreate' ]
182
- : [],
74
+ if ( ! shouldDestroy ) {
75
+ spinner.fail(
76
+ `Aborted. Run 'wp-env destroy' manually or start with '--runtime=${ savedRuntime }'.`
77
+ );
78
+ process.exit( 1 );
183
79
  }
184
- );
185
80
 
186
- if ( config.env.development.phpmyadminPort ) {
187
- await dockerCompose.upOne( 'phpmyadmin', {
188
- ...dockerComposeConfig,
189
- commandOptions: shouldConfigureWp
190
- ? [ '--build', '--force-recreate' ]
191
- : [],
192
- } );
81
+ // User confirmed - destroy old runtime first.
82
+ spinner.start();
83
+ spinner.text = `Destroying previous ${ savedRuntime } environment.`;
84
+ const oldRuntime = getRuntime( savedRuntime );
85
+ await oldRuntime.destroy( config, { spinner } );
193
86
  }
194
87
 
195
- if ( config.env.tests.phpmyadminPort ) {
196
- await dockerCompose.upOne( 'tests-phpmyadmin', {
197
- ...dockerComposeConfig,
198
- commandOptions: shouldConfigureWp
199
- ? [ '--build', '--force-recreate' ]
200
- : [],
201
- } );
88
+ if ( ! config.detectedLocalConfig ) {
89
+ const { configDirectoryPath } = config;
90
+ spinner.warn(
91
+ `Warning: could not find a .wp-env.json configuration file and could not determine if '${ configDirectoryPath }' is a WordPress installation, a plugin, or a theme.`
92
+ );
93
+ spinner.start();
202
94
  }
203
95
 
204
- // Make sure we've consumed the custom CLI dockerfile.
205
- if ( shouldConfigureWp ) {
206
- await dockerCompose.buildOne( [ 'cli' ], { ...dockerComposeConfig } );
96
+ if ( config.testsEnvironment !== false ) {
97
+ spinner.warn(
98
+ 'Warning: wp-env starts both development and tests environments by default.\n' +
99
+ 'This behavior is deprecated and will be removed in a future version.\n' +
100
+ 'To avoid this warning, add "testsEnvironment": false to your .wp-env.json.\n' +
101
+ 'The "env", "testsPort", and "testsEnvironment" options are also deprecated.\n' +
102
+ 'Use the --config option with a separate config file for test environments instead.\n'
103
+ );
104
+ spinner.start();
207
105
  }
208
106
 
209
- // Only run WordPress install/configuration when config has changed.
210
- if ( shouldConfigureWp ) {
211
- spinner.text = 'Configuring WordPress.';
107
+ let result;
108
+ try {
109
+ result = await runtime.start( config, {
110
+ spinner,
111
+ update,
112
+ xdebug,
113
+ spx,
114
+ debug,
115
+ } );
212
116
 
117
+ // Save the runtime type after successful start.
118
+ await saveRuntime( runtimeName, config.workDirectoryPath );
119
+ } catch ( error ) {
120
+ // Attempt to stop any partially-started environment so that
121
+ // processes do not linger after a failed start.
213
122
  try {
214
- await checkDatabaseConnection( config );
215
- } catch ( error ) {
216
- // Wait 30 seconds for MySQL to accept connections.
217
- await retry( () => checkDatabaseConnection( config ), {
218
- times: 30,
219
- delay: 1000,
220
- } );
221
-
222
- // It takes 3-4 seconds for MySQL to be ready after it starts accepting connections.
223
- await sleep( 4000 );
123
+ await runtime.stop( config, { spinner } );
124
+ } catch {
125
+ // Ignore cleanup errors.
224
126
  }
225
-
226
- // Retry WordPress installation in case MySQL *still* wasn't ready.
227
- await Promise.all( [
228
- retry( () => configureWordPress( 'development', config, spinner ), {
229
- times: 2,
230
- } ),
231
- retry( () => configureWordPress( 'tests', config, spinner ), {
232
- times: 2,
233
- } ),
234
- ] );
235
-
236
- // Set the cache key once everything has been configured.
237
- await setCache( CONFIG_CACHE_KEY, configHash, {
238
- workDirectoryPath,
239
- } );
127
+ throw error;
240
128
  }
241
129
 
242
130
  if ( scripts ) {
243
131
  await executeLifecycleScript( 'afterStart', config, spinner );
244
132
  }
245
133
 
246
- const siteUrl = config.env.development.config.WP_SITEURL;
247
- const testsSiteUrl = config.env.tests.config.WP_SITEURL;
248
-
249
- const mySQLPort = await getPublicDockerPort(
250
- 'mysql',
251
- 3306,
252
- dockerComposeConfig
253
- );
254
-
255
- const testsMySQLPort = await getPublicDockerPort(
256
- 'tests-mysql',
257
- 3306,
258
- dockerComposeConfig
259
- );
260
-
261
- const phpmyadminPort = config.env.development.phpmyadminPort
262
- ? await getPublicDockerPort( 'phpmyadmin', 80, dockerComposeConfig )
263
- : null;
264
-
265
- const testsPhpmyadminPort = config.env.tests.phpmyadminPort
266
- ? await getPublicDockerPort(
267
- 'tests-phpmyadmin',
268
- 80,
269
- dockerComposeConfig
270
- )
271
- : null;
272
-
273
- spinner.prefixText = [
274
- 'WordPress development site started' +
275
- ( siteUrl ? ` at ${ siteUrl }` : '.' ),
276
- 'WordPress test site started' +
277
- ( testsSiteUrl ? ` at ${ testsSiteUrl }` : '.' ),
278
- `MySQL is listening on port ${ mySQLPort }`,
279
- `MySQL for automated testing is listening on port ${ testsMySQLPort }`,
280
- phpmyadminPort &&
281
- `phpMyAdmin started at http://localhost:${ phpmyadminPort }`,
282
- testsPhpmyadminPort &&
283
- `phpMyAdmin for automated testing started at http://localhost:${ testsPhpmyadminPort }`,
284
- ]
285
- .filter( Boolean )
286
- .join( '\n' );
134
+ spinner.prefixText = result.message;
287
135
  spinner.prefixText += '\n\n';
288
136
  spinner.text = 'Done!';
289
137
  };
290
138
 
291
- async function getPublicDockerPort(
292
- service,
293
- containerPort,
294
- dockerComposeConfig
295
- ) {
296
- const { out: address } = await dockerCompose.port(
297
- service,
298
- containerPort,
299
- dockerComposeConfig
300
- );
301
- return address.split( ':' ).pop().trim();
302
- }
303
-
304
139
  /**
305
140
  * Checks for legacy installs and provides
306
141
  * the user the option to delete them.
@@ -0,0 +1,160 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const path = require( 'path' );
6
+ const { existsSync } = require( 'fs' );
7
+ const chalk = require( 'chalk' );
8
+
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ const { loadConfig } = require( '../config' );
13
+ const { getRuntime, detectRuntime } = require( '../runtime' );
14
+
15
+ /**
16
+ * Check if an environment has been initialized by looking for runtime-specific files.
17
+ *
18
+ * @param {Object} config The wp-env configuration object.
19
+ * @return {boolean} True if the environment has been initialized.
20
+ */
21
+ function isEnvironmentInitialized( config ) {
22
+ // Check for Docker's docker-compose.yml
23
+ if ( existsSync( config.dockerComposeConfigPath ) ) {
24
+ return true;
25
+ }
26
+
27
+ // Check for Playground's blueprint file
28
+ const playgroundBlueprintPath = path.join(
29
+ config.workDirectoryPath,
30
+ 'playground-blueprint.json'
31
+ );
32
+ if ( existsSync( playgroundBlueprintPath ) ) {
33
+ return true;
34
+ }
35
+
36
+ return false;
37
+ }
38
+
39
+ /**
40
+ * Outputs the status of the wp-env environment.
41
+ *
42
+ * @param {Object} options
43
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
44
+ * @param {boolean} options.debug True if debug mode is enabled.
45
+ * @param {boolean} options.json True to output as JSON.
46
+ * @param {string|null} options.config Path to a custom .wp-env.json configuration file.
47
+ */
48
+ module.exports = async function status( {
49
+ spinner,
50
+ debug,
51
+ json,
52
+ config: customConfigPath,
53
+ } ) {
54
+ spinner.text = 'Getting environment status.';
55
+
56
+ const config = await loadConfig( path.resolve( '.' ), customConfigPath );
57
+
58
+ // Check if environment is initialized by looking for runtime-specific files.
59
+ // We check for these files specifically because the work directory may exist
60
+ // just from caching the WordPress version, but these files are only created
61
+ // when `wp-env start` is actually run.
62
+ if ( ! isEnvironmentInitialized( config ) ) {
63
+ spinner.stop();
64
+ if ( json ) {
65
+ console.log(
66
+ JSON.stringify( {
67
+ status: 'uninitialized',
68
+ installPath: config.workDirectoryPath,
69
+ configPath: config.configDirectoryPath,
70
+ } )
71
+ );
72
+ } else {
73
+ console.log( formatNotInitialized( config ) );
74
+ }
75
+ return;
76
+ }
77
+
78
+ // Detect and get runtime.
79
+ const runtimeName = await detectRuntime( config.workDirectoryPath );
80
+ const runtime = getRuntime( runtimeName );
81
+
82
+ // Get status from runtime.
83
+ const statusData = await runtime.getStatus( config, { spinner, debug } );
84
+
85
+ spinner.stop();
86
+ if ( json ) {
87
+ console.log( JSON.stringify( statusData ) );
88
+ } else {
89
+ console.log( formatStatus( statusData ) );
90
+ }
91
+ };
92
+
93
+ /**
94
+ * Format status for human-readable output when not initialized.
95
+ *
96
+ * @param {Object} config The config object.
97
+ * @return {string} Formatted output.
98
+ */
99
+ function formatNotInitialized( config ) {
100
+ const indent = ' - ';
101
+ return `
102
+ ${ chalk.bold( 'status' ) }: ${ chalk.red( 'uninitialized' ) }
103
+ ${ indent }install path: ${ chalk.dim( config.workDirectoryPath ) }
104
+ ${ indent }config: ${ chalk.dim( config.configDirectoryPath ) }
105
+
106
+ ${ chalk.dim( 'Run `wp-env start` to initialize the environment.' ) }
107
+ `;
108
+ }
109
+
110
+ /**
111
+ * Format status data for human-readable output.
112
+ *
113
+ * @param {Object} status The status object from runtime.
114
+ * @return {string} Formatted output.
115
+ */
116
+ function formatStatus( status ) {
117
+ const statusColor = status.status === 'running' ? chalk.green : chalk.red;
118
+ const indent = ' - ';
119
+
120
+ let output = `
121
+ ${ chalk.bold( 'status' ) }: ${ statusColor( status.status ) }
122
+ ${ indent }runtime: ${ chalk.dim( status.runtime ) }
123
+ ${ indent }install path: ${ chalk.dim( status.installPath ) }
124
+ ${ indent }config: ${ chalk.dim( status.configPath ) }
125
+ `;
126
+
127
+ // Environment section.
128
+ output += `\n${ chalk.bold( 'environment' ) }:\n`;
129
+ if ( status.urls?.development ) {
130
+ output += `${ indent }url: ${ chalk.dim( status.urls.development ) }\n`;
131
+ }
132
+ output += `${ indent }multisite: ${ chalk.dim(
133
+ status.config?.multisite ? 'yes' : 'no'
134
+ ) }\n`;
135
+ output += `${ indent }xdebug: ${ chalk.dim(
136
+ status.config?.xdebug || 'off'
137
+ ) }\n`;
138
+ if ( status.ports?.development ) {
139
+ output += `${ indent }http port: ${ chalk.dim(
140
+ status.ports.development
141
+ ) }\n`;
142
+ }
143
+ if ( status.urls?.phpmyadmin ) {
144
+ output += `${ indent }phpmyadmin url: ${ chalk.dim(
145
+ status.urls.phpmyadmin
146
+ ) }\n`;
147
+ }
148
+ if ( status.ports?.mysql ) {
149
+ output += `${ indent }mysql port: ${ chalk.dim(
150
+ status.ports.mysql
151
+ ) }\n`;
152
+ }
153
+ if ( status.ports?.tests ) {
154
+ output += `${ indent }test http port: ${ chalk.dim(
155
+ status.ports.tests
156
+ ) }\n`;
157
+ }
158
+
159
+ return output;
160
+ }
@@ -2,32 +2,30 @@
2
2
  /**
3
3
  * External dependencies
4
4
  */
5
- const { v2: dockerCompose } = require( 'docker-compose' );
5
+ const path = require( 'path' );
6
6
 
7
7
  /**
8
8
  * Internal dependencies
9
9
  */
10
- const initConfig = require( '../init-config' );
10
+ const { loadConfig } = require( '../config' );
11
+ const { getRuntime, detectRuntime } = require( '../runtime' );
11
12
 
12
13
  /**
13
14
  * Stops the development server.
14
15
  *
15
- * @param {Object} options
16
- * @param {Object} options.spinner A CLI spinner which indicates progress.
17
- * @param {boolean} options.debug True if debug mode is enabled.
16
+ * @param {Object} options
17
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
18
+ * @param {boolean} options.debug True if debug mode is enabled.
19
+ * @param {string|null} options.config Path to a custom .wp-env.json configuration file.
18
20
  */
19
- module.exports = async function stop( { spinner, debug } ) {
20
- const { dockerComposeConfigPath } = await initConfig( {
21
- spinner,
22
- debug,
23
- } );
24
-
25
- spinner.text = 'Stopping WordPress.';
26
-
27
- await dockerCompose.down( {
28
- config: dockerComposeConfigPath,
29
- log: debug,
30
- } );
31
-
32
- spinner.text = 'Stopped WordPress.';
21
+ module.exports = async function stop( {
22
+ spinner,
23
+ debug,
24
+ config: customConfigPath,
25
+ } ) {
26
+ const config = await loadConfig( path.resolve( '.' ), customConfigPath );
27
+ const runtime = getRuntime(
28
+ await detectRuntime( config.workDirectoryPath )
29
+ );
30
+ await runtime.stop( config, { spinner, debug } );
33
31
  };
@@ -23,7 +23,6 @@ const { checkPort, checkVersion, checkString } = require( './validate-config' );
23
23
  * @property {?number} phpmyadminPort An override for the development environment's phpMyAdmin port.
24
24
  * @property {?WPSource} coreSource An override for all environment's coreSource.
25
25
  * @property {?string} phpVersion An override for all environment's PHP version.
26
- * @property {?boolean} multisite An override for if environmen should be multisite.
27
26
  * @property {?Object.<string, string>} lifecycleScripts An override for various lifecycle scripts.
28
27
  */
29
28
 
@@ -69,10 +68,6 @@ module.exports = function getConfigFromEnvironmentVars( cacheDirectoryPath ) {
69
68
  environmentConfig.phpVersion = process.env.WP_ENV_PHP_VERSION;
70
69
  }
71
70
 
72
- if ( process.env.WP_ENV_MULTISITE ) {
73
- environmentConfig.multisite = !! process.env.WP_ENV_MULTISITE;
74
- }
75
-
76
71
  return environmentConfig;
77
72
  };
78
73
 
@@ -108,6 +103,8 @@ function getLifecycleScriptOverrides() {
108
103
  const lifecycleEnvironmentVars = {
109
104
  WP_ENV_LIFECYCLE_SCRIPT_AFTER_START: 'afterStart',
110
105
  WP_ENV_LIFECYCLE_SCRIPT_AFTER_CLEAN: 'afterClean',
106
+ WP_ENV_LIFECYCLE_SCRIPT_AFTER_RESET: 'afterReset',
107
+ WP_ENV_LIFECYCLE_SCRIPT_AFTER_CLEANUP: 'afterCleanup',
111
108
  WP_ENV_LIFECYCLE_SCRIPT_AFTER_DESTROY: 'afterDestroy',
112
109
  };
113
110
  for ( const envVar in lifecycleEnvironmentVars ) {
@@ -11,6 +11,7 @@ const fs = require( 'fs' ).promises;
11
11
  const getCacheDirectory = require( './get-cache-directory' );
12
12
  const md5 = require( '../md5' );
13
13
  const { parseConfig, getConfigFilePath } = require( './parse-config' );
14
+ const { ValidationError } = require( './validate-config' );
14
15
  const postProcessConfig = require( './post-process-config' );
15
16
 
16
17
  /**
@@ -35,12 +36,31 @@ const postProcessConfig = require( './post-process-config' );
35
36
  /**
36
37
  * Loads any configuration from a given directory.
37
38
  *
38
- * @param {string} configDirectoryPath The directory we want to load the config from.
39
+ * @param {string} configDirectoryPath The directory we want to load the config from.
40
+ * @param {string|null} customConfigPath Optional custom config file path.
39
41
  *
40
42
  * @return {Promise<WPConfig>} The config object we've loaded.
41
43
  */
42
- module.exports = async function loadConfig( configDirectoryPath ) {
43
- const configFilePath = getConfigFilePath( configDirectoryPath );
44
+ module.exports = async function loadConfig(
45
+ configDirectoryPath,
46
+ customConfigPath = null
47
+ ) {
48
+ const configFilePath = getConfigFilePath(
49
+ configDirectoryPath,
50
+ 'local',
51
+ customConfigPath
52
+ );
53
+
54
+ // If a custom config path was provided, verify the file exists.
55
+ if ( customConfigPath ) {
56
+ try {
57
+ await fs.stat( configFilePath );
58
+ } catch ( error ) {
59
+ throw new ValidationError(
60
+ `Config file not found: ${ configFilePath }`
61
+ );
62
+ }
63
+ }
44
64
 
45
65
  const cacheDirectoryPath = path.resolve(
46
66
  await getCacheDirectory(),
@@ -49,7 +69,11 @@ module.exports = async function loadConfig( configDirectoryPath ) {
49
69
 
50
70
  // Parse any configuration we found in the given directory.
51
71
  // This comes merged and prepared for internal consumption.
52
- let config = await parseConfig( configDirectoryPath, cacheDirectoryPath );
72
+ let config = await parseConfig(
73
+ configDirectoryPath,
74
+ cacheDirectoryPath,
75
+ customConfigPath
76
+ );
53
77
 
54
78
  // Make sure to perform any additional post-processing that
55
79
  // may be needed before the config object is ready for
@@ -64,9 +88,15 @@ module.exports = async function loadConfig( configDirectoryPath ) {
64
88
  ),
65
89
  configDirectoryPath,
66
90
  workDirectoryPath: cacheDirectoryPath,
91
+ customConfigPath,
92
+ testsEnvironment: config.testsEnvironment !== false,
67
93
  detectedLocalConfig: await hasLocalConfig( [
68
94
  configFilePath,
69
- getConfigFilePath( configDirectoryPath, 'override' ),
95
+ getConfigFilePath(
96
+ configDirectoryPath,
97
+ 'override',
98
+ customConfigPath
99
+ ),
70
100
  ] ),
71
101
  lifecycleScripts: config.lifecycleScripts,
72
102
  env: config.env,