@wordpress/env 4.8.0 → 5.1.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.
package/CHANGELOG.md CHANGED
@@ -2,7 +2,29 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 5.1.0 (2022-08-10)
6
+
7
+ ### Enhancement
8
+ - Previously, wp-env used the WordPress version provided by Docker in the WordPress image for installations which don't specify a WordPress version. Now, wp-env will find the latest stable version on WordPress.org and check out the https://github.com/WordPress/WordPress repository at the tag matching that version. In most cases, this will match what Docker provides. The benefit is that wp-env (and WordPress.org) now controls the default WordPress version rather than Docker.
9
+
10
+ ### Bug Fix
11
+ - Downloading a default WordPress version also resolves a bug where the wrong WordPress test files were used if no core source was specified in wp-env.json. The current trunk test files were downloaded rather than the stable version. Now, the test files will match the default stable version.
12
+
13
+ ## 5.0.0 (2022-07-27)
14
+
15
+ ### Breaking Changes
16
+ - Removed the `WP_PHPUNIT__TESTS_CONFIG` environment variable from the `phpunit` container. **This removes automatic support for the `wp-phpunit/wp-phpunit` Composer package. To continue using the package, set the following two environment variables in your `phpunit.xml` file or similar: `WP_TESTS_DIR=""` and `WP_PHPUNIT__TESTS_CONFIG="/wordpress-phpunit/wp-tests-config.php"`.**
17
+ - Removed the generated `/var/www/html/phpunit-wp-config.php` file from the environment.
18
+
19
+ ### Enhancement
20
+ - Read WordPress' version and include the corresponding PHPUnit test files in the environment.
21
+ - Set the `WP_TESTS_DIR` environment variable in all containers to point at the PHPUnit test files.
22
+
23
+ ### Bug Fix
24
+ - Restrict `WP_TESTS_DOMAIN` constant to just hostname rather than an entire URL (e.g. it now excludes scheme, port, etc.) ([#41039](https://github.com/WordPress/gutenberg/pull/41039)).
25
+
5
26
  ## 4.8.0 (2022-06-01)
27
+
6
28
  ### Enhancement
7
29
  - Removed the need for quotation marks when passing options to `wp-env run`.
8
30
  - Setting a `config` key to `null` will prevent adding the constant to `wp-config.php` even if a default value is defined by `wp-env`.
package/README.md CHANGED
@@ -16,7 +16,7 @@ The local environment will be available at http://localhost:8888 (Username: `adm
16
16
 
17
17
  ## Prerequisites
18
18
 
19
- `wp-env` requires Docker to be installed. There are instructions available for installing Docker on [Windows 10 Pro](https://docs.docker.com/docker-for-windows/install/), [all other versions of Windows](https://docs.docker.com/toolbox/toolbox_install_windows/), [macOS](https://docs.docker.com/docker-for-mac/install/), and [Linux](https://docs.docker.com/v17.12/install/linux/docker-ce/ubuntu/#install-using-the-convenience-script).
19
+ `wp-env` requires Docker to be installed. There are instructions available for installing Docker on [Windows](https://docs.docker.com/desktop/install/windows-install/), [macOS](https://docs.docker.com/docker-for-mac/install/), and [Linux](https://docs.docker.com/desktop/install/linux-install/).
20
20
 
21
21
  Node.js and NPM are required. The latest LTS version of Node.js is used to develop `wp-env` and is recommended.
22
22
 
@@ -40,7 +40,9 @@ If your project already has a package.json, it's also possible to use `wp-env` a
40
40
  $ npm i @wordpress/env --save-dev
41
41
  ```
42
42
 
43
- Then modify your package.json and add an extra command to npm `scripts` (https://docs.npmjs.com/misc/scripts):
43
+ At this point, you can use the local, project-level version of wp-env via [`npx`](https://www.npmjs.com/package/npx), a utility automatically installed with `npm`.`npx` finds binaries like wp-env installed through node modules. As an example: `npx wp-env start --update`.
44
+
45
+ If you don't wish to use `npx`, modify your package.json and add an extra command to npm `scripts` (https://docs.npmjs.com/misc/scripts):
44
46
 
45
47
  ```json
46
48
  "scripts": {
@@ -51,7 +53,7 @@ Then modify your package.json and add an extra command to npm `scripts` (https:/
51
53
  When installing `wp-env` in this way, all `wp-env` commands detailed in these docs must be prefixed with `npm run`, for example:
52
54
 
53
55
  ```sh
54
- # You must add another double dash to pass the "update" flag to wp-env
56
+ # You must add another double dash to pass flags to the script (wp-env) rather than to npm itself
55
57
  $ npm run wp-env start -- --update
56
58
  ```
57
59
 
@@ -117,15 +119,14 @@ Running `docker ps` and inspecting the `PORTS` column allows you to determine wh
117
119
 
118
120
  You may also specify the port numbers in your `.wp-env.json` file, but the environment variables take precedent.
119
121
 
120
- ### 3. Restart `wp-env`
122
+ ### 3. Restart `wp-env` with updates
121
123
 
122
124
  Restarting `wp-env` will restart the underlying Docker containers which can fix many issues.
123
125
 
124
- To restart `wp-env`:
126
+ To restart `wp-env`, just run `wp-env start` again. It will automatically stop and start the container. If you also pass the `--update` argument, it will download updates and configure WordPress agian.
125
127
 
126
128
  ```sh
127
- $ wp-env stop
128
- $ wp-env start
129
+ $ wp-env start --update
129
130
  ```
130
131
 
131
132
  ### 4. Restart Docker
@@ -156,16 +157,17 @@ $ wp-env clean all
156
157
  $ wp-env start
157
158
  ```
158
159
 
159
- ### 6. Nuke everything and start again 🔥
160
+ ### 6. Destroy everything and start again 🔥
160
161
 
161
- When all else fails, you can use `wp-env destroy` to forcibly remove all of the underlying Docker containers and volumes. This will allow you to start from scratch.
162
+ When all else fails, you can use `wp-env destroy` to forcibly remove all of the underlying Docker containers, volumes, and files. This will allow you to start from scratch.
162
163
 
163
- To nuke everything:
164
+ To do so:
164
165
 
165
166
  **⚠️ WARNING: This will permanently delete any posts, pages, media, etc. in the local WordPress installation.**
166
167
 
167
168
  ```sh
168
169
  $ wp-env destroy
170
+ # This new instance is a fresh start with no existing data:
169
171
  $ wp-env start
170
172
  ```
171
173
 
@@ -188,6 +190,14 @@ wp-env start --debug
188
190
  ...
189
191
  ```
190
192
 
193
+ ## Using included WordPress PHPUnit test files
194
+
195
+ Out of the box `wp-env` includes the [WordPress' PHPUnit test files](https://develop.svn.wordpress.org/trunk/tests/phpunit/) corresponding to the version of WordPress installed. There is an environment variable, `WP_TESTS_DIR`, which points to the location of these files within each container. By including these files in the environment, we remove the need for you to use a package or install and mount them yourself. If you do not want to use these files, you should ignore the `WP_TESTS_DIR` environment variable and load them from the location of your choosing.
196
+
197
+ ### Customizing the `wp-tests-config.php` file
198
+
199
+ While we do provide a default `wp-tests-config.php` file within the environment, there may be cases where you want to use your own. WordPress provides a `WP_TESTS_CONFIG_FILE_PATH` constant that you can use to change the `wp-config.php` file used for testing. Set this to a desired path in your `bootstrap.php` file and the file you've chosen will be used instead of the one included in the environment.
200
+
191
201
  ## Using Xdebug
192
202
 
193
203
  Xdebug is installed in the wp-env environment, but it is turned off by default. To enable Xdebug, you can use the `--xdebug` flag with the `wp-env start` command. Here is a reference to how the flag works:
@@ -203,10 +213,12 @@ wp-env start
203
213
  wp-env start --xdebug=profile,trace,debug
204
214
  ```
205
215
 
206
- When you're running `wp-env` using `npm run`, like when working in the Gutenberg repo or when having `wp-env` as a local project dependency, don't forget to add an extra double dash before the `--xdebug` command:
216
+ When you're running `wp-env` using `npm run`, like when working in the Gutenberg repo or when `wp-env` is a local project dependency, don't forget to add an extra double dash before the `--xdebug` command:
207
217
 
208
218
  ```sh
209
219
  npm run wp-env start -- --xdebug
220
+ # Alternatively, use npx:
221
+ npx wp-env start --xdebug
210
222
  ```
211
223
 
212
224
  If you forget about that, the `--xdebug` parameter will be passed to NPM instead of the `wp-env start` command and it will be ignored.
@@ -511,7 +523,7 @@ SCRIPT_DEBUG: true,
511
523
  WP_PHP_BINARY: 'php',
512
524
  WP_TESTS_EMAIL: 'admin@example.org',
513
525
  WP_TESTS_TITLE: 'Test Blog',
514
- WP_TESTS_DOMAIN: 'http://localhost',
526
+ WP_TESTS_DOMAIN: 'localhost',
515
527
  WP_SITEURL: 'http://localhost',
516
528
  WP_HOME: 'http://localhost',
517
529
  ```
@@ -524,7 +536,7 @@ Additionally, the values referencing a URL include the specified port for the gi
524
536
 
525
537
  ### Examples
526
538
 
527
- #### Latest production WordPress + current directory as a plugin
539
+ #### Latest stable WordPress + current directory as a plugin
528
540
 
529
541
  This is useful for plugin development.
530
542
 
@@ -19,13 +19,18 @@ const { dbEnv } = require( './config' );
19
19
  /**
20
20
  * Gets the volume mounts for an individual service.
21
21
  *
22
- * @param {WPServiceConfig} config The service config to get the mounts from.
23
- * @param {string} wordpressDefault The default internal path for the WordPress
24
- * source code (such as tests-wordpress).
22
+ * @param {string} workDirectoryPath The working directory for wp-env.
23
+ * @param {WPServiceConfig} config The service config to get the mounts from.
24
+ * @param {string} wordpressDefault The default internal path for the WordPress
25
+ * source code (such as tests-wordpress).
25
26
  *
26
27
  * @return {string[]} An array of volumes to mount in string format.
27
28
  */
28
- function getMounts( config, wordpressDefault = 'wordpress' ) {
29
+ function getMounts(
30
+ workDirectoryPath,
31
+ config,
32
+ wordpressDefault = 'wordpress'
33
+ ) {
29
34
  // Top-level WordPress directory mounts (like wp-content/themes)
30
35
  const directoryMounts = Object.entries( config.mappings ).map(
31
36
  ( [ wpDir, source ] ) => `${ source.path }:/var/www/html/${ wpDir }`
@@ -45,9 +50,19 @@ function getMounts( config, wordpressDefault = 'wordpress' ) {
45
50
  config.coreSource ? config.coreSource.path : wordpressDefault
46
51
  }:/var/www/html`;
47
52
 
53
+ const corePHPUnitMount = `${ path.join(
54
+ workDirectoryPath,
55
+ wordpressDefault === 'wordpress'
56
+ ? 'WordPress-PHPUnit'
57
+ : 'tests-WordPress-PHPUnit',
58
+ 'tests',
59
+ 'phpunit'
60
+ ) }:/wordpress-phpunit`;
61
+
48
62
  return [
49
63
  ...new Set( [
50
64
  coreMount,
65
+ corePHPUnitMount,
51
66
  ...directoryMounts,
52
67
  ...pluginMounts,
53
68
  ...themeMounts,
@@ -64,8 +79,15 @@ function getMounts( config, wordpressDefault = 'wordpress' ) {
64
79
  * @return {Object} A docker-compose config object, ready to serialize into YAML.
65
80
  */
66
81
  module.exports = function buildDockerComposeConfig( config ) {
67
- const developmentMounts = getMounts( config.env.development );
68
- const testsMounts = getMounts( config.env.tests, 'tests-wordpress' );
82
+ const developmentMounts = getMounts(
83
+ config.workDirectoryPath,
84
+ config.env.development
85
+ );
86
+ const testsMounts = getMounts(
87
+ config.workDirectoryPath,
88
+ config.env.tests,
89
+ 'tests-wordpress'
90
+ );
69
91
 
70
92
  // When both tests and development reference the same WP source, we need to
71
93
  // ensure that tests pulls from a copy of the files so that it maintains
@@ -208,6 +230,7 @@ module.exports = function buildDockerComposeConfig( config ) {
208
230
  environment: {
209
231
  ...dbEnv.credentials,
210
232
  ...dbEnv.development,
233
+ WP_TESTS_DIR: '/wordpress-phpunit',
211
234
  },
212
235
  volumes: developmentMounts,
213
236
  },
@@ -218,6 +241,7 @@ module.exports = function buildDockerComposeConfig( config ) {
218
241
  environment: {
219
242
  ...dbEnv.credentials,
220
243
  ...dbEnv.tests,
244
+ WP_TESTS_DIR: '/wordpress-phpunit',
221
245
  },
222
246
  volumes: testsMounts,
223
247
  },
@@ -229,6 +253,7 @@ module.exports = function buildDockerComposeConfig( config ) {
229
253
  environment: {
230
254
  ...dbEnv.credentials,
231
255
  ...dbEnv.development,
256
+ WP_TESTS_DIR: '/wordpress-phpunit',
232
257
  },
233
258
  },
234
259
  'tests-cli': {
@@ -239,6 +264,7 @@ module.exports = function buildDockerComposeConfig( config ) {
239
264
  environment: {
240
265
  ...dbEnv.credentials,
241
266
  ...dbEnv.tests,
267
+ WP_TESTS_DIR: '/wordpress-phpunit',
242
268
  },
243
269
  },
244
270
  composer: {
@@ -256,8 +282,7 @@ module.exports = function buildDockerComposeConfig( config ) {
256
282
  ],
257
283
  environment: {
258
284
  LOCAL_DIR: 'html',
259
- WP_PHPUNIT__TESTS_CONFIG:
260
- '/var/www/html/phpunit-wp-config.php',
285
+ WP_TESTS_DIR: '/wordpress-phpunit',
261
286
  ...dbEnv.credentials,
262
287
  ...dbEnv.tests,
263
288
  },
package/lib/cli.js CHANGED
@@ -22,57 +22,61 @@ const wpRed = boldWhite.bgHex( '#d94f4f' );
22
22
  const wpYellow = boldWhite.bgHex( '#f0b849' );
23
23
 
24
24
  // Spinner.
25
- const withSpinner = ( command ) => ( ...args ) => {
26
- const spinner = ora().start();
27
- args[ 0 ].spinner = spinner;
28
- let time = process.hrtime();
29
- return command( ...args ).then(
30
- ( message ) => {
31
- time = process.hrtime( time );
32
- spinner.succeed(
33
- `${ message || spinner.text } (in ${ time[ 0 ] }s ${ (
34
- time[ 1 ] / 1e6
35
- ).toFixed( 0 ) }ms)`
36
- );
37
- process.exit( 0 );
38
- },
39
- ( error ) => {
40
- if ( error instanceof env.ValidationError ) {
41
- // Error is a validation error. That means the user did something wrong.
42
- spinner.fail( error.message );
43
- process.exit( 1 );
44
- } else if (
45
- error &&
46
- typeof error === 'object' &&
47
- 'exitCode' in error &&
48
- 'err' in error &&
49
- 'out' in error
50
- ) {
51
- // Error is a docker-compose error. That means something docker-related failed.
52
- // https://github.com/PDMLab/docker-compose/blob/HEAD/src/index.ts
53
- spinner.fail( 'Error while running docker-compose command.' );
54
- if ( error.out ) {
55
- process.stdout.write( error.out );
56
- }
57
- if ( error.err ) {
58
- process.stderr.write( error.err );
59
- }
60
- process.exit( error.exitCode );
61
- } else if ( error ) {
62
- // Error is an unknown error. That means there was a bug in our code.
63
- spinner.fail(
64
- typeof error === 'string' ? error : error.message
25
+ const withSpinner =
26
+ ( command ) =>
27
+ ( ...args ) => {
28
+ const spinner = ora().start();
29
+ args[ 0 ].spinner = spinner;
30
+ let time = process.hrtime();
31
+ return command( ...args ).then(
32
+ ( message ) => {
33
+ time = process.hrtime( time );
34
+ spinner.succeed(
35
+ `${ message || spinner.text } (in ${ time[ 0 ] }s ${ (
36
+ time[ 1 ] / 1e6
37
+ ).toFixed( 0 ) }ms)`
65
38
  );
66
- // Disable reason: Using console.error() means we get a stack trace.
67
- console.error( error );
68
- process.exit( 1 );
69
- } else {
70
- spinner.fail( 'An unknown error occured.' );
71
- process.exit( 1 );
39
+ process.exit( 0 );
40
+ },
41
+ ( error ) => {
42
+ if ( error instanceof env.ValidationError ) {
43
+ // Error is a validation error. That means the user did something wrong.
44
+ spinner.fail( error.message );
45
+ process.exit( 1 );
46
+ } else if (
47
+ error &&
48
+ typeof error === 'object' &&
49
+ 'exitCode' in error &&
50
+ 'err' in error &&
51
+ 'out' in error
52
+ ) {
53
+ // Error is a docker-compose error. That means something docker-related failed.
54
+ // https://github.com/PDMLab/docker-compose/blob/HEAD/src/index.ts
55
+ spinner.fail(
56
+ 'Error while running docker-compose command.'
57
+ );
58
+ if ( error.out ) {
59
+ process.stdout.write( error.out );
60
+ }
61
+ if ( error.err ) {
62
+ process.stderr.write( error.err );
63
+ }
64
+ process.exit( error.exitCode );
65
+ } else if ( error ) {
66
+ // Error is an unknown error. That means there was a bug in our code.
67
+ spinner.fail(
68
+ typeof error === 'string' ? error : error.message
69
+ );
70
+ // Disable reason: Using console.error() means we get a stack trace.
71
+ console.error( error );
72
+ process.exit( 1 );
73
+ } else {
74
+ spinner.fail( 'An unknown error occured.' );
75
+ process.exit( 1 );
76
+ }
72
77
  }
73
- }
74
- );
75
- };
78
+ );
79
+ };
76
80
 
77
81
  module.exports = function cli() {
78
82
  // Do nothing if Docker is unavailable.
@@ -9,7 +9,7 @@ const { spawn } = require( 'child_process' );
9
9
  const initConfig = require( '../init-config' );
10
10
 
11
11
  /**
12
- * @typedef {import('../config').Config} Config
12
+ * @typedef {import('../config').WPConfig} WPConfig
13
13
  */
14
14
 
15
15
  /**
@@ -42,11 +42,11 @@ module.exports = async function run( { container, command, spinner, debug } ) {
42
42
  /**
43
43
  * Runs an arbitrary command on the given Docker container.
44
44
  *
45
- * @param {Object} options
46
- * @param {string} options.container The Docker container to run the command on.
47
- * @param {string} options.command The command to run.
48
- * @param {Config} options.config The wp-env configuration.
49
- * @param {Object} options.spinner A CLI spinner which indicates progress.
45
+ * @param {Object} options
46
+ * @param {string} options.container The Docker container to run the command on.
47
+ * @param {string} options.command The command to run.
48
+ * @param {WPConfig} options.config The wp-env configuration.
49
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
50
50
  */
51
51
  function spawnCommandDirectly( { container, command, config, spinner } ) {
52
52
  const composeCommand = [
@@ -21,16 +21,18 @@ const retry = require( '../retry' );
21
21
  const stop = require( './stop' );
22
22
  const initConfig = require( '../init-config' );
23
23
  const downloadSources = require( '../download-sources' );
24
+ const downloadWPPHPUnit = require( '../download-wp-phpunit' );
24
25
  const {
25
26
  checkDatabaseConnection,
26
27
  configureWordPress,
27
28
  setupWordPressDirectories,
29
+ readWordPressVersion,
28
30
  } = require( '../wordpress' );
29
31
  const { didCacheChange, setCache } = require( '../cache' );
30
32
  const md5 = require( '../md5' );
31
33
 
32
34
  /**
33
- * @typedef {import('../config').Config} Config
35
+ * @typedef {import('../config').WPConfig} WPConfig
34
36
  */
35
37
  const CONFIG_CACHE_KEY = 'config_checksum';
36
38
 
@@ -127,7 +129,25 @@ module.exports = async function start( { spinner, debug, update, xdebug } ) {
127
129
  ] );
128
130
 
129
131
  if ( shouldConfigureWp ) {
132
+ spinner.text = 'Setting up WordPress directories';
133
+
130
134
  await setupWordPressDirectories( config );
135
+
136
+ // Use the WordPress versions to download the PHPUnit suite.
137
+ const wpVersions = await Promise.all( [
138
+ readWordPressVersion(
139
+ config.env.development.coreSource,
140
+ spinner,
141
+ debug
142
+ ),
143
+ readWordPressVersion( config.env.tests.coreSource, spinner, debug ),
144
+ ] );
145
+ await downloadWPPHPUnit(
146
+ config,
147
+ { development: wpVersions[ 0 ], tests: wpVersions[ 1 ] },
148
+ spinner,
149
+ debug
150
+ );
131
151
  }
132
152
 
133
153
  spinner.text = 'Starting WordPress.';
@@ -173,7 +193,7 @@ module.exports = async function start( { spinner, debug, update, xdebug } ) {
173
193
  }
174
194
 
175
195
  const siteUrl = config.env.development.config.WP_SITEURL;
176
- const e2eSiteUrl = config.env.tests.config.WP_TESTS_DOMAIN;
196
+ const e2eSiteUrl = `http://${ config.env.tests.config.WP_TESTS_DOMAIN }:${ config.env.tests.port }/`;
177
197
 
178
198
  const { out: mySQLAddress } = await dockerCompose.port(
179
199
  'mysql',
@@ -15,6 +15,7 @@ const readRawConfigFile = require( './read-raw-config-file' );
15
15
  const parseConfig = require( './parse-config' );
16
16
  const { includeTestsPath, parseSourceString } = parseConfig;
17
17
  const md5 = require( '../md5' );
18
+ const { getLatestWordPressVersion } = require( '../wordpress' );
18
19
 
19
20
  /**
20
21
  * wp-env configuration.
@@ -33,7 +34,7 @@ const md5 = require( '../md5' );
33
34
  * Base-level config for any particular environment. (development/tests/etc)
34
35
  *
35
36
  * @typedef WPServiceConfig
36
- * @property {?WPSource} coreSource The WordPress installation to load in the environment.
37
+ * @property {WPSource} coreSource The WordPress installation to load in the environment.
37
38
  * @property {WPSource[]} pluginSources Plugins to load in the environment.
38
39
  * @property {WPSource[]} themeSources Themes to load in the environment.
39
40
  * @property {number} port The port to use.
@@ -68,9 +69,37 @@ module.exports = async function readConfig( configPath ) {
68
69
  md5( configPath )
69
70
  );
70
71
 
72
+ // The specified base configuration from .wp-env.json or from the local
73
+ // source type which was automatically detected.
74
+ const baseConfig =
75
+ ( await readRawConfigFile( '.wp-env.json', configPath ) ) ||
76
+ ( await getDefaultBaseConfig( configPath ) );
77
+
78
+ // Overriden .wp-env.json on a per-user case.
79
+ const overrideConfig =
80
+ ( await readRawConfigFile(
81
+ '.wp-env.override.json',
82
+ configPath.replace( /\.wp-env\.json$/, '.wp-env.override.json' )
83
+ ) ) || {};
84
+
85
+ const detectedLocalConfig =
86
+ Object.keys( { ...baseConfig, ...overrideConfig } ).length > 0;
87
+
88
+ // If there is no local WordPress version, use the latest stable version from GitHub.
89
+ let coreRef;
90
+ if ( ! overrideConfig.core && ! baseConfig.core ) {
91
+ const wpVersion = await getLatestWordPressVersion();
92
+ if ( ! wpVersion ) {
93
+ throw new ValidationError(
94
+ 'Could not find the latest WordPress version. There may be a network issue.'
95
+ );
96
+ }
97
+ coreRef = `WordPress/WordPress#${ wpVersion }`;
98
+ }
99
+
71
100
  // Default configuration which is overridden by .wp-env.json files.
72
101
  const defaultConfiguration = {
73
- core: null,
102
+ core: coreRef,
74
103
  phpVersion: null,
75
104
  plugins: [],
76
105
  themes: [],
@@ -83,7 +112,7 @@ module.exports = async function readConfig( configPath ) {
83
112
  WP_PHP_BINARY: 'php',
84
113
  WP_TESTS_EMAIL: 'admin@example.org',
85
114
  WP_TESTS_TITLE: 'Test Blog',
86
- WP_TESTS_DOMAIN: 'http://localhost',
115
+ WP_TESTS_DOMAIN: 'localhost',
87
116
  WP_SITEURL: 'http://localhost',
88
117
  WP_HOME: 'http://localhost',
89
118
  },
@@ -96,22 +125,6 @@ module.exports = async function readConfig( configPath ) {
96
125
  },
97
126
  };
98
127
 
99
- // The specified base configuration from .wp-env.json or from the local
100
- // source type which was automatically detected.
101
- const baseConfig =
102
- ( await readRawConfigFile( '.wp-env.json', configPath ) ) ||
103
- ( await getDefaultBaseConfig( configPath ) );
104
-
105
- // Overriden .wp-env.json on a per-user case.
106
- const overrideConfig =
107
- ( await readRawConfigFile(
108
- '.wp-env.override.json',
109
- configPath.replace( /\.wp-env\.json$/, '.wp-env.override.json' )
110
- ) ) || {};
111
-
112
- const detectedLocalConfig =
113
- Object.keys( { ...baseConfig, ...overrideConfig } ).length > 0;
114
-
115
128
  // A quick validation before merging on a service by service level allows us
116
129
  // to check the root configuration options and provide more helpful errors.
117
130
  validateConfig(
@@ -297,7 +310,6 @@ function withOverrides( config ) {
297
310
  };
298
311
 
299
312
  // Update wp config options to include the correct port number in the URL.
300
- updateEnvUrl( 'WP_TESTS_DOMAIN' );
301
313
  updateEnvUrl( 'WP_SITEURL' );
302
314
  updateEnvUrl( 'WP_HOME' );
303
315
 
@@ -17,11 +17,10 @@ Object {
17
17
  "WP_HOME": "http://localhost:2000/",
18
18
  "WP_PHP_BINARY": "php",
19
19
  "WP_SITEURL": "http://localhost:2000/",
20
- "WP_TESTS_DOMAIN": "http://localhost:2000/",
20
+ "WP_TESTS_DOMAIN": "localhost",
21
21
  "WP_TESTS_EMAIL": "admin@example.org",
22
22
  "WP_TESTS_TITLE": "Test Blog",
23
23
  },
24
- "coreSource": null,
25
24
  "mappings": Object {},
26
25
  "phpVersion": null,
27
26
  "pluginSources": Array [],
@@ -40,11 +39,10 @@ Object {
40
39
  "WP_HOME": "http://localhost:1000/",
41
40
  "WP_PHP_BINARY": "php",
42
41
  "WP_SITEURL": "http://localhost:1000/",
43
- "WP_TESTS_DOMAIN": "http://localhost:1000/",
42
+ "WP_TESTS_DOMAIN": "localhost",
44
43
  "WP_TESTS_EMAIL": "admin@example.org",
45
44
  "WP_TESTS_TITLE": "Test Blog",
46
45
  },
47
- "coreSource": null,
48
46
  "mappings": Object {},
49
47
  "phpVersion": null,
50
48
  "pluginSources": Array [],
@@ -64,7 +62,7 @@ Object {
64
62
  "WP_HOME": "http://localhost:8889/",
65
63
  "WP_PHP_BINARY": "php",
66
64
  "WP_SITEURL": "http://localhost:8889/",
67
- "WP_TESTS_DOMAIN": "http://localhost:8889/",
65
+ "WP_TESTS_DOMAIN": "localhost",
68
66
  "WP_TESTS_EMAIL": "admin@example.org",
69
67
  "WP_TESTS_TITLE": "Test Blog",
70
68
  }
@@ -78,7 +76,7 @@ Object {
78
76
  "WP_HOME": "http://localhost:8888/",
79
77
  "WP_PHP_BINARY": "php",
80
78
  "WP_SITEURL": "http://localhost:8888/",
81
- "WP_TESTS_DOMAIN": "http://localhost:8888/",
79
+ "WP_TESTS_DOMAIN": "localhost",
82
80
  "WP_TESTS_EMAIL": "admin@example.org",
83
81
  "WP_TESTS_TITLE": "Test Blog",
84
82
  }
@@ -19,6 +19,23 @@ jest.mock( 'fs', () => ( {
19
19
  },
20
20
  } ) );
21
21
 
22
+ // This mocks a small response with a format matching the stable-check API.
23
+ // It makes getLatestWordPressVersion resolve to "100.0.0".
24
+ jest.mock( 'got', () =>
25
+ jest.fn( ( url ) => ( {
26
+ json: () => {
27
+ if ( url === 'https://api.wordpress.org/core/stable-check/1.0/' ) {
28
+ return Promise.resolve( {
29
+ '1.0': 'insecure',
30
+ '99.1.1': 'outdated',
31
+ '100.0.0': 'latest',
32
+ '100.0.1': 'fancy',
33
+ } );
34
+ }
35
+ },
36
+ } ) )
37
+ );
38
+
22
39
  jest.mock( '../detect-directory-type', () => jest.fn() );
23
40
 
24
41
  describe( 'readConfig', () => {
@@ -59,19 +76,38 @@ describe( 'readConfig', () => {
59
76
  );
60
77
  detectDirectoryType.mockImplementation( () => 'core' );
61
78
  const config = await readConfig( '.wp-env.json' );
62
- expect( config.env.development.coreSource ).not.toBeNull();
79
+ expect( config.env.development.coreSource.type ).toBe( 'local' );
63
80
  expect( config.env.tests.coreSource ).not.toBeNull();
64
81
  expect( config.env.development.pluginSources ).toHaveLength( 0 );
65
82
  expect( config.env.development.themeSources ).toHaveLength( 0 );
66
83
  } );
67
84
 
85
+ it( 'should use the most recent stable WordPress version for the default core source', async () => {
86
+ readFile.mockImplementation( () =>
87
+ Promise.resolve( JSON.stringify( {} ) )
88
+ );
89
+ const config = await readConfig( '.wp-env.json' );
90
+
91
+ const expected = {
92
+ url: 'https://github.com/WordPress/WordPress.git',
93
+ type: 'git',
94
+ basename: 'WordPress',
95
+ ref: '100.0.0', // From the mock of https at the top of the file.
96
+ };
97
+
98
+ expect( config.env.development.coreSource ).toMatchObject(
99
+ expected
100
+ );
101
+ expect( config.env.tests.coreSource ).toMatchObject( expected );
102
+ } );
103
+
68
104
  it( 'should infer a plugin config when ran from a plugin directory', async () => {
69
105
  readFile.mockImplementation( () =>
70
106
  Promise.reject( { code: 'ENOENT' } )
71
107
  );
72
108
  detectDirectoryType.mockImplementation( () => 'plugin' );
73
109
  const config = await readConfig( '.wp-env.json' );
74
- expect( config.env.development.coreSource ).toBeNull();
110
+ expect( config.env.development.coreSource.type ).toBe( 'git' );
75
111
  expect( config.env.development.pluginSources ).toHaveLength( 1 );
76
112
  expect( config.env.tests.pluginSources ).toHaveLength( 1 );
77
113
  expect( config.env.development.themeSources ).toHaveLength( 0 );
@@ -83,8 +119,8 @@ describe( 'readConfig', () => {
83
119
  );
84
120
  detectDirectoryType.mockImplementation( () => 'theme' );
85
121
  const config = await readConfig( '.wp-env.json' );
86
- expect( config.env.development.coreSource ).toBeNull();
87
- expect( config.env.tests.coreSource ).toBeNull();
122
+ expect( config.env.development.coreSource.type ).toBe( 'git' );
123
+ expect( config.env.tests.coreSource.type ).toBe( 'git' );
88
124
  expect( config.env.development.themeSources ).toHaveLength( 1 );
89
125
  expect( config.env.tests.themeSources ).toHaveLength( 1 );
90
126
  expect( config.env.development.pluginSources ).toHaveLength( 0 );
@@ -197,6 +233,11 @@ describe( 'readConfig', () => {
197
233
  // Remove generated values which are different on other machines.
198
234
  delete config.dockerComposeConfigPath;
199
235
  delete config.workDirectoryPath;
236
+
237
+ // This encodes both the version of WordPress (which can change frequently)
238
+ // as well as the wp-env directory which is unique on every machine.
239
+ delete config.env.development.coreSource;
240
+ delete config.env.tests.coreSource;
200
241
  expect( config ).toMatchSnapshot();
201
242
  } );
202
243
  } );
@@ -265,17 +306,17 @@ describe( 'readConfig', () => {
265
306
  pluginSources: [
266
307
  {
267
308
  type: 'local',
268
- path: expect.stringMatching( /^(\/||\\).*relative$/ ),
309
+ path: expect.stringMatching( /^(\/|\\).*relative$/ ),
269
310
  basename: 'relative',
270
311
  },
271
312
  {
272
313
  type: 'local',
273
- path: expect.stringMatching( /^(\/||\\).*parent$/ ),
314
+ path: expect.stringMatching( /^(\/|\\).*parent$/ ),
274
315
  basename: 'parent',
275
316
  },
276
317
  {
277
318
  type: 'local',
278
- path: expect.stringMatching( /^(\/||\\).*home$/ ),
319
+ path: expect.stringMatching( /^(\/|\\).*home$/ ),
279
320
  basename: 'home',
280
321
  },
281
322
  ],
@@ -284,17 +325,17 @@ describe( 'readConfig', () => {
284
325
  pluginSources: [
285
326
  {
286
327
  type: 'local',
287
- path: expect.stringMatching( /^(\/||\\).*relative$/ ),
328
+ path: expect.stringMatching( /^(\/|\\).*relative$/ ),
288
329
  basename: 'relative',
289
330
  },
290
331
  {
291
332
  type: 'local',
292
- path: expect.stringMatching( /^(\/||\\).*parent$/ ),
333
+ path: expect.stringMatching( /^(\/|\\).*parent$/ ),
293
334
  basename: 'parent',
294
335
  },
295
336
  {
296
337
  type: 'local',
297
- path: expect.stringMatching( /^(\/||\\).*home$/ ),
338
+ path: expect.stringMatching( /^(\/|\\).*home$/ ),
298
339
  basename: 'home',
299
340
  },
300
341
  ],
@@ -324,28 +365,28 @@ describe( 'readConfig', () => {
324
365
  expect( config.env.development.pluginSources ).toEqual( [
325
366
  {
326
367
  type: 'local',
327
- path: expect.stringMatching( /^(\/||\\).*test1a$/ ),
368
+ path: expect.stringMatching( /^(\/|\\).*test1a$/ ),
328
369
  basename: 'test1a',
329
370
  },
330
371
  ] );
331
372
  expect( config.env.development.themeSources ).toEqual( [
332
373
  {
333
374
  type: 'local',
334
- path: expect.stringMatching( /^(\/||\\).*test2a$/ ),
375
+ path: expect.stringMatching( /^(\/|\\).*test2a$/ ),
335
376
  basename: 'test2a',
336
377
  },
337
378
  ] );
338
379
  expect( config.env.tests.pluginSources ).toEqual( [
339
380
  {
340
381
  type: 'local',
341
- path: expect.stringMatching( /^(\/||\\).*test1b$/ ),
382
+ path: expect.stringMatching( /^(\/|\\).*test1b$/ ),
342
383
  basename: 'test1b',
343
384
  },
344
385
  ] );
345
386
  expect( config.env.tests.themeSources ).toEqual( [
346
387
  {
347
388
  type: 'local',
348
- path: expect.stringMatching( /^(\/||\\).*test2b$/ ),
389
+ path: expect.stringMatching( /^(\/|\\).*test2b$/ ),
349
390
  basename: 'test2b',
350
391
  },
351
392
  ] );
@@ -359,18 +400,18 @@ describe( 'readConfig', () => {
359
400
  expect( config.env.development ).toMatchObject( {
360
401
  coreSource: {
361
402
  type: 'local',
362
- path: expect.stringMatching( /^(\/||\\).*relative$/ ),
403
+ path: expect.stringMatching( /^(\/|\\).*relative$/ ),
363
404
  testsPath: expect.stringMatching(
364
- /^(\/||\\).*tests-relative$/
405
+ /^(\/|\\).*tests-relative$/
365
406
  ),
366
407
  },
367
408
  } );
368
409
  expect( config.env.tests ).toMatchObject( {
369
410
  coreSource: {
370
411
  type: 'local',
371
- path: expect.stringMatching( /^(\/||\\).*relative$/ ),
412
+ path: expect.stringMatching( /^(\/|\\).*relative$/ ),
372
413
  testsPath: expect.stringMatching(
373
- /^(\/||\\).*tests-relative$/
414
+ /^(\/|\\).*tests-relative$/
374
415
  ),
375
416
  },
376
417
  } );
@@ -396,30 +437,29 @@ describe( 'readConfig', () => {
396
437
  type: 'git',
397
438
  url: 'https://github.com/WordPress/gutenberg.git',
398
439
  ref: undefined,
399
- path: expect.stringMatching( /^(\/||\\).*gutenberg$/ ),
440
+ path: expect.stringMatching( /^(\/|\\).*gutenberg$/ ),
400
441
  basename: 'gutenberg',
401
442
  },
402
443
  {
403
444
  type: 'git',
404
445
  url: 'https://github.com/WordPress/gutenberg.git',
405
446
  ref: 'trunk',
406
- path: expect.stringMatching( /^(\/||\\).*gutenberg$/ ),
447
+ path: expect.stringMatching( /^(\/|\\).*gutenberg$/ ),
407
448
  basename: 'gutenberg',
408
449
  },
409
450
  {
410
451
  type: 'git',
411
452
  url: 'https://github.com/WordPress/gutenberg.git',
412
453
  ref: '5.0',
413
- path: expect.stringMatching( /^(\/||\\).*gutenberg$/ ),
454
+ path: expect.stringMatching( /^(\/|\\).*gutenberg$/ ),
414
455
  basename: 'gutenberg',
415
456
  },
416
457
  {
417
458
  type: 'git',
418
- url:
419
- 'https://github.com/WordPress/theme-experiments.git',
459
+ url: 'https://github.com/WordPress/theme-experiments.git',
420
460
  ref: 'tt1-blocks@0.4.3',
421
461
  path: expect.stringMatching(
422
- /^(\/||\\).*theme-experiments(\/||\\)tt1-blocks$/
462
+ /^(\/|\\).*theme-experiments(\/|\\)tt1-blocks$/
423
463
  ),
424
464
  basename: 'tt1-blocks',
425
465
  },
@@ -447,33 +487,29 @@ describe( 'readConfig', () => {
447
487
  pluginSources: [
448
488
  {
449
489
  type: 'zip',
450
- url:
451
- 'https://downloads.wordpress.org/plugin/gutenberg.zip',
452
- path: expect.stringMatching( /^(\/||\\).*gutenberg$/ ),
490
+ url: 'https://downloads.wordpress.org/plugin/gutenberg.zip',
491
+ path: expect.stringMatching( /^(\/|\\).*gutenberg$/ ),
453
492
  basename: 'gutenberg',
454
493
  },
455
494
  {
456
495
  type: 'zip',
457
- url:
458
- 'https://downloads.wordpress.org/plugin/gutenberg.8.1.0.zip',
459
- path: expect.stringMatching( /^(\/||\\).*gutenberg$/ ),
496
+ url: 'https://downloads.wordpress.org/plugin/gutenberg.8.1.0.zip',
497
+ path: expect.stringMatching( /^(\/|\\).*gutenberg$/ ),
460
498
  basename: 'gutenberg',
461
499
  },
462
500
  {
463
501
  type: 'zip',
464
- url:
465
- 'https://downloads.wordpress.org/theme/twentytwenty.zip',
502
+ url: 'https://downloads.wordpress.org/theme/twentytwenty.zip',
466
503
  path: expect.stringMatching(
467
- /^(\/||\\).*twentytwenty$/
504
+ /^(\/|\\).*twentytwenty$/
468
505
  ),
469
506
  basename: 'twentytwenty',
470
507
  },
471
508
  {
472
509
  type: 'zip',
473
- url:
474
- 'https://downloads.wordpress.org/theme/twentytwenty.1.3.zip',
510
+ url: 'https://downloads.wordpress.org/theme/twentytwenty.1.3.zip',
475
511
  path: expect.stringMatching(
476
- /^(\/||\\).*twentytwenty$/
512
+ /^(\/|\\).*twentytwenty$/
477
513
  ),
478
514
  basename: 'twentytwenty',
479
515
  },
@@ -502,35 +538,31 @@ describe( 'readConfig', () => {
502
538
  pluginSources: [
503
539
  {
504
540
  type: 'zip',
505
- url:
506
- 'https://www.example.com/test/path/to/gutenberg.zip',
507
- path: expect.stringMatching( /^(\/||\\).*gutenberg$/ ),
541
+ url: 'https://www.example.com/test/path/to/gutenberg.zip',
542
+ path: expect.stringMatching( /^(\/|\\).*gutenberg$/ ),
508
543
  basename: 'gutenberg',
509
544
  },
510
545
  {
511
546
  type: 'zip',
512
- url:
513
- 'https://www.example.com/test/path/to/gutenberg.8.1.0.zip',
547
+ url: 'https://www.example.com/test/path/to/gutenberg.8.1.0.zip',
514
548
  path: expect.stringMatching(
515
- /^(\/||\\).*gutenberg.8.1.0$/
549
+ /^(\/|\\).*gutenberg.8.1.0$/
516
550
  ),
517
551
  basename: 'gutenberg.8.1.0',
518
552
  },
519
553
  {
520
554
  type: 'zip',
521
- url:
522
- 'https://www.example.com/test/path/to/twentytwenty.zip',
555
+ url: 'https://www.example.com/test/path/to/twentytwenty.zip',
523
556
  path: expect.stringMatching(
524
- /^(\/||\\).*twentytwenty$/
557
+ /^(\/|\\).*twentytwenty$/
525
558
  ),
526
559
  basename: 'twentytwenty',
527
560
  },
528
561
  {
529
562
  type: 'zip',
530
- url:
531
- 'https://www.example.com/test/path/to/twentytwenty.1.3.zip',
563
+ url: 'https://www.example.com/test/path/to/twentytwenty.1.3.zip',
532
564
  path: expect.stringMatching(
533
- /^(\/||\\).*twentytwenty.1.3$/
565
+ /^(\/|\\).*twentytwenty.1.3$/
534
566
  ),
535
567
  basename: 'twentytwenty.1.3',
536
568
  },
@@ -538,7 +570,7 @@ describe( 'readConfig', () => {
538
570
  type: 'zip',
539
571
  url: 'https://example.com/twentytwenty.1.3.zip',
540
572
  path: expect.stringMatching(
541
- /^(\/||\\).*twentytwenty.1.3$/
573
+ /^(\/|\\).*twentytwenty.1.3$/
542
574
  ),
543
575
  basename: 'twentytwenty.1.3',
544
576
  },
@@ -580,12 +612,12 @@ describe( 'readConfig', () => {
580
612
  const matchObj = {
581
613
  test: {
582
614
  type: 'local',
583
- path: expect.stringMatching( /^(\/||\\).*relative$/ ),
615
+ path: expect.stringMatching( /^(\/|\\).*relative$/ ),
584
616
  basename: 'relative',
585
617
  },
586
618
  test2: {
587
619
  type: 'git',
588
- path: expect.stringMatching( /^(\/||\\).*gutenberg$/ ),
620
+ path: expect.stringMatching( /^(\/|\\).*gutenberg$/ ),
589
621
  basename: 'gutenberg',
590
622
  },
591
623
  };
@@ -813,7 +845,7 @@ describe( 'readConfig', () => {
813
845
  development: {
814
846
  port: 1000,
815
847
  config: {
816
- WP_TESTS_DOMAIN: 'http://localhost:1000/',
848
+ WP_TESTS_DOMAIN: 'localhost',
817
849
  WP_SITEURL: 'http://localhost:1000/',
818
850
  WP_HOME: 'http://localhost:1000/',
819
851
  },
@@ -821,7 +853,7 @@ describe( 'readConfig', () => {
821
853
  tests: {
822
854
  port: 2000,
823
855
  config: {
824
- WP_TESTS_DOMAIN: 'http://localhost:2000/',
856
+ WP_TESTS_DOMAIN: 'localhost',
825
857
  WP_SITEURL: 'http://localhost:2000/',
826
858
  WP_HOME: 'http://localhost:2000/',
827
859
  },
@@ -849,7 +881,7 @@ describe( 'readConfig', () => {
849
881
  development: {
850
882
  port: 1000,
851
883
  config: {
852
- WP_TESTS_DOMAIN: 'http://localhost:1000/',
884
+ WP_TESTS_DOMAIN: 'localhost',
853
885
  WP_SITEURL: 'http://localhost:1000/',
854
886
  WP_HOME: 'http://localhost:3000/',
855
887
  },
@@ -857,7 +889,7 @@ describe( 'readConfig', () => {
857
889
  tests: {
858
890
  port: 2000,
859
891
  config: {
860
- WP_TESTS_DOMAIN: 'http://localhost:2000/',
892
+ WP_TESTS_DOMAIN: 'localhost',
861
893
  WP_SITEURL: 'http://localhost:2000/',
862
894
  WP_HOME: 'http://localhost:3000/',
863
895
  },
@@ -1112,7 +1144,7 @@ describe( 'readConfig', () => {
1112
1144
  WP_PHP_BINARY: 'php',
1113
1145
  WP_TESTS_EMAIL: 'admin@example.org',
1114
1146
  WP_TESTS_TITLE: 'Test Blog',
1115
- WP_TESTS_DOMAIN: 'http://localhost:8889/',
1147
+ WP_TESTS_DOMAIN: 'localhost',
1116
1148
  WP_SITEURL: 'http://localhost:8889/',
1117
1149
  WP_HOME: 'http://localhost:8889/',
1118
1150
  } );
@@ -1126,7 +1158,7 @@ describe( 'readConfig', () => {
1126
1158
  WP_PHP_BINARY: 'php',
1127
1159
  WP_TESTS_EMAIL: 'admin@example.org',
1128
1160
  WP_TESTS_TITLE: 'Test Blog',
1129
- WP_TESTS_DOMAIN: 'http://localhost:8888/',
1161
+ WP_TESTS_DOMAIN: 'localhost',
1130
1162
  WP_SITEURL: 'http://localhost:8888/',
1131
1163
  WP_HOME: 'http://localhost:8888/',
1132
1164
  } );
@@ -16,7 +16,7 @@ const extractZip = util.promisify( require( 'extract-zip' ) );
16
16
  const rimraf = util.promisify( require( 'rimraf' ) );
17
17
 
18
18
  /**
19
- * @typedef {import('./config').Config} Config
19
+ * @typedef {import('./config').WPConfig} WPConfig
20
20
  * @typedef {import('./config').WPSource} WPSource
21
21
  */
22
22
 
@@ -24,8 +24,8 @@ const rimraf = util.promisify( require( 'rimraf' ) );
24
24
  * Download each source for each environment. If the same source is used in
25
25
  * multiple environments, it will only be downloaded once.
26
26
  *
27
- * @param {Config} config The wp-env configuration object.
28
- * @param {Object} spinner The spinner object to show progress.
27
+ * @param {WPConfig} config The wp-env configuration object.
28
+ * @param {Object} spinner The spinner object to show progress.
29
29
  * @return {Promise} Returns a promise which resolves when the downloads finish.
30
30
  */
31
31
  module.exports = function downloadSources( config, spinner ) {
@@ -0,0 +1,140 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const SimpleGit = require( 'simple-git' );
6
+ const fs = require( 'fs' );
7
+ const path = require( 'path' );
8
+
9
+ /**
10
+ * @typedef {import('./config').WPConfig} WPConfig
11
+ */
12
+
13
+ /**
14
+ * Downloads the WordPress PHPUnit files for each environment into the appropriate directories.
15
+ *
16
+ * @param {WPConfig} config The wp-env config object.
17
+ * @param {Object} wpVersions The WordPress versions for each environment.
18
+ * @param {Object} spinner The spinner object to show progress.
19
+ * @param {boolean} debug Indicates whether or not debug mode is active.
20
+ * @return {Promise} Returns a promise which resolves when the downloads finish.
21
+ */
22
+ module.exports = function downloadWPPHPUnit(
23
+ config,
24
+ wpVersions,
25
+ spinner,
26
+ debug
27
+ ) {
28
+ const progresses = {};
29
+ const getProgressSetter = ( id ) => ( progress ) => {
30
+ progresses[ id ] = progress;
31
+ spinner.text =
32
+ 'Downloading WordPress PHPUnit Suite.\n' +
33
+ Object.entries( progresses )
34
+ .map(
35
+ ( [ key, value ] ) =>
36
+ ` - ${ key }: ${ ( value * 100 ).toFixed( 0 ) }/100%`
37
+ )
38
+ .join( '\n' );
39
+ };
40
+
41
+ const promises = [];
42
+ for ( const env in config.env ) {
43
+ const wpVersion = wpVersions[ env ] ? wpVersions[ env ] : null;
44
+ const directory = path.join(
45
+ config.workDirectoryPath,
46
+ env === 'development'
47
+ ? 'WordPress-PHPUnit'
48
+ : 'tests-WordPress-PHPUnit'
49
+ );
50
+ promises.push(
51
+ downloadTestSuite( directory, wpVersion, {
52
+ onProgress: getProgressSetter,
53
+ spinner,
54
+ debug,
55
+ } )
56
+ );
57
+ }
58
+
59
+ return Promise.all( promises );
60
+ };
61
+
62
+ /**
63
+ * Downloads the PHPUnit tests for a given WordPress version into the appropriate directory.
64
+ *
65
+ * @param {string} directory The directory to place the PHPUnit tests in.
66
+ * @param {string} wpVersion The version of WordPress to install PHPUnit tests for. Trunk when empty.
67
+ * @param {Object} options
68
+ * @param {Function} options.onProgress A function called with download progress. Will be invoked with one argument: a number that ranges from 0 to 1 which indicates current download progress for this source.
69
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
70
+ * @param {boolean} options.debug True if debug mode is enabled.
71
+ */
72
+ async function downloadTestSuite(
73
+ directory,
74
+ wpVersion,
75
+ { onProgress, spinner, debug }
76
+ ) {
77
+ const log = debug
78
+ ? ( message ) => {
79
+ spinner.info( `SimpleGit: ${ message }` );
80
+ spinner.start();
81
+ }
82
+ : () => {};
83
+ onProgress( 0 );
84
+
85
+ const progressHandler = ( { progress } ) => {
86
+ onProgress( progress / 100 );
87
+ };
88
+
89
+ // Make sure that the version is in X.X.X format. This is required
90
+ // because WordPress/wordpress-develop uses X.X.X tags but
91
+ // WordPress uses X.X for non-patch releases.
92
+ if ( wpVersion && wpVersion.match( /^[0-9]+.[0-9]+$/ ) ) {
93
+ wpVersion += '.0';
94
+ }
95
+
96
+ log( 'Cloning or getting the PHPUnit suite from GitHub.' );
97
+ const git = SimpleGit( { progress: progressHandler } );
98
+
99
+ const isRepo =
100
+ fs.existsSync( directory ) &&
101
+ ( await git.cwd( directory ).checkIsRepo( 'root' ) );
102
+
103
+ if ( isRepo ) {
104
+ log( 'Repo already exists, using it.' );
105
+ } else {
106
+ await git.clone(
107
+ 'https://github.com/WordPress/wordpress-develop.git',
108
+ directory,
109
+ {
110
+ '--depth': '1',
111
+ '--no-checkout': null,
112
+ }
113
+ );
114
+ await git.cwd( directory );
115
+
116
+ // We use a sparse checkout to minimize the amount of data we need to download.
117
+ log( 'Enabling sparse checkout.' );
118
+ await git.raw( 'sparse-checkout', 'set', '--cone', 'tests/phpunit' );
119
+ }
120
+
121
+ // Figure out the ref that we need to checkout to get the correct version of the PHPUnit library.
122
+ // Alpha, Beta, and RC versions are bleeding edge and should pull from trunk.
123
+ let ref;
124
+ const fetchRaw = [];
125
+ if ( ! wpVersion || wpVersion.match( /-(?:alpha|beta|rc)/ ) ) {
126
+ ref = 'trunk';
127
+ fetchRaw.push( 'fetch', 'origin', ref, '--depth', '1' );
128
+ } else {
129
+ ref = `tags/${ wpVersion }`;
130
+ fetchRaw.push( 'fetch', 'origin', 'tag', wpVersion, '--depth', '1' );
131
+ }
132
+
133
+ log( `Fetching ${ ref }.` );
134
+ await git.raw( fetchRaw );
135
+
136
+ log( `Checking out ${ ref }.` );
137
+ await git.checkout( ref );
138
+
139
+ onProgress( 1 );
140
+ }
package/lib/wordpress.js CHANGED
@@ -5,6 +5,7 @@ const dockerCompose = require( 'docker-compose' );
5
5
  const util = require( 'util' );
6
6
  const fs = require( 'fs' ).promises;
7
7
  const path = require( 'path' );
8
+ const got = require( 'got' );
8
9
 
9
10
  /**
10
11
  * Promisified dependencies
@@ -14,6 +15,7 @@ const copyDir = util.promisify( require( 'copy-dir' ) );
14
15
  /**
15
16
  * @typedef {import('./config').WPConfig} WPConfig
16
17
  * @typedef {import('./config').WPServiceConfig} WPServiceConfig
18
+ * @typedef {import('./config').WPSource} WPSource
17
19
  * @typedef {'development'|'tests'} WPEnvironment
18
20
  * @typedef {'development'|'tests'|'all'} WPEnvironmentSelection
19
21
  */
@@ -101,25 +103,15 @@ async function configureWordPress( environment, config, spinner ) {
101
103
  }
102
104
  );
103
105
 
104
- /**
105
- * Since wp-phpunit loads wp-settings.php at the end of its wp-config.php
106
- * file, we need to avoid loading it too early in our own wp-config.php. If
107
- * we load it too early, then some things (like MULTISITE) will be defined
108
- * before wp-phpunit has a chance to configure them. To avoid this, create a
109
- * copy of wp-config.php for phpunit which doesn't require wp-settings.php.
110
- *
111
- * Note that This needs to be executed using `exec` on the wordpress service
112
- * so that file permissions work properly.
113
- *
114
- * This will be removed in the future. @see https://github.com/WordPress/gutenberg/issues/23171
115
- *
116
- */
106
+ // WordPress' PHPUnit suite expects a `wp-tests-config.php` in
107
+ // the directory that the test suite is contained within.
108
+ // Make sure ABSPATH points to the WordPress install.
117
109
  await dockerCompose.exec(
118
110
  environment === 'development' ? 'wordpress' : 'tests-wordpress',
119
111
  [
120
112
  'sh',
121
113
  '-c',
122
- 'sed "/^require.*wp-settings.php/d" /var/www/html/wp-config.php > /var/www/html/phpunit-wp-config.php && chmod 777 /var/www/html/phpunit-wp-config.php',
114
+ `sed -e "/^require.*wp-settings.php/d" -e "s/define( 'ABSPATH', __DIR__ . '\\/' );/define( 'ABSPATH', '\\/var\\/www\\/html\\/' );\\n\\tdefine( 'WP_DEFAULT_THEME', 'default' );/" /var/www/html/wp-config.php > /wordpress-phpunit/wp-tests-config.php`,
123
115
  ],
124
116
  {
125
117
  config: config.dockerComposeConfigPath,
@@ -246,10 +238,63 @@ async function copyCoreFiles( fromPath, toPath ) {
246
238
  } );
247
239
  }
248
240
 
241
+ /**
242
+ * Scans through a WordPress source to find the version of WordPress it contains.
243
+ *
244
+ * @param {WPSource} coreSource The WordPress source.
245
+ * @param {Object} spinner A CLI spinner which indicates progress.
246
+ * @param {boolean} debug Indicates whether or not the CLI is in debug mode.
247
+ * @return {string} The version of WordPress the source is for.
248
+ */
249
+ async function readWordPressVersion( coreSource, spinner, debug ) {
250
+ const versionFilePath = path.join(
251
+ coreSource.path,
252
+ 'wp-includes',
253
+ 'version.php'
254
+ );
255
+ const versionFile = await fs.readFile( versionFilePath, {
256
+ encoding: 'utf-8',
257
+ } );
258
+ const versionMatch = versionFile.match(
259
+ /\$wp_version = '([A-Za-z\-0-9.]+)'/
260
+ );
261
+ if ( ! versionMatch ) {
262
+ throw new Error( `Failed to find version in ${ versionFilePath }` );
263
+ }
264
+
265
+ if ( debug ) {
266
+ spinner.info(
267
+ `Found WordPress ${ versionMatch[ 1 ] } in ${ versionFilePath }.`
268
+ );
269
+ }
270
+
271
+ return versionMatch[ 1 ];
272
+ }
273
+
274
+ /**
275
+ * Returns the latest stable version of WordPress by requesting the stable-check
276
+ * endpoint on WordPress.org.
277
+ *
278
+ * @return {string} The latest stable version of WordPress, like "6.0.1"
279
+ */
280
+ async function getLatestWordPressVersion() {
281
+ const versions = await got(
282
+ 'https://api.wordpress.org/core/stable-check/1.0/'
283
+ ).json();
284
+
285
+ for ( const [ version, status ] of Object.entries( versions ) ) {
286
+ if ( status === 'latest' ) {
287
+ return version;
288
+ }
289
+ }
290
+ }
291
+
249
292
  module.exports = {
250
293
  hasSameCoreSource,
251
294
  checkDatabaseConnection,
252
295
  configureWordPress,
253
296
  resetDatabase,
254
297
  setupWordPressDirectories,
298
+ readWordPressVersion,
299
+ getLatestWordPressVersion,
255
300
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/env",
3
- "version": "4.8.0",
3
+ "version": "5.1.0",
4
4
  "description": "A zero-config, self contained local WordPress environment for development and testing.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -36,7 +36,7 @@
36
36
  "copy-dir": "^1.3.0",
37
37
  "docker-compose": "^0.22.2",
38
38
  "extract-zip": "^1.6.7",
39
- "got": "^10.7.0",
39
+ "got": "^11.8.5",
40
40
  "inquirer": "^7.1.0",
41
41
  "js-yaml": "^3.13.1",
42
42
  "ora": "^4.0.2",
@@ -51,5 +51,5 @@
51
51
  "scripts": {
52
52
  "test": "echo \"Error: run tests from root\" && exit 1"
53
53
  },
54
- "gitHead": "a3e0b62091e8a8bdf5e2518e42d60d7098af48cc"
54
+ "gitHead": "08358f53b627a15148c3a3e433cdf58cf8714aa4"
55
55
  }