diff --git a/packages/env/CHANGELOG.md b/packages/env/CHANGELOG.md index 0c128c813684f7..8467cf5c7b6d87 100644 --- a/packages/env/CHANGELOG.md +++ b/packages/env/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +### Enhancement +- 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. + +### Bug Fix +- 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. + ## 5.0.0 (2022-07-27) ### Breaking Changes diff --git a/packages/env/README.md b/packages/env/README.md index fad2d008a3ce90..af7aa69b3beb53 100644 --- a/packages/env/README.md +++ b/packages/env/README.md @@ -16,7 +16,7 @@ The local environment will be available at http://localhost:8888 (Username: `adm ## Prerequisites -`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). +`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/). Node.js and NPM are required. The latest LTS version of Node.js is used to develop `wp-env` and is recommended. @@ -40,7 +40,9 @@ If your project already has a package.json, it's also possible to use `wp-env` a $ npm i @wordpress/env --save-dev ``` -Then modify your package.json and add an extra command to npm `scripts` (https://docs.npmjs.com/misc/scripts): +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`. + +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): ```json "scripts": { @@ -51,7 +53,7 @@ Then modify your package.json and add an extra command to npm `scripts` (https:/ When installing `wp-env` in this way, all `wp-env` commands detailed in these docs must be prefixed with `npm run`, for example: ```sh -# You must add another double dash to pass the "update" flag to wp-env +# You must add another double dash to pass flags to the script (wp-env) rather than to npm itself $ npm run wp-env start -- --update ``` @@ -117,15 +119,14 @@ Running `docker ps` and inspecting the `PORTS` column allows you to determine wh You may also specify the port numbers in your `.wp-env.json` file, but the environment variables take precedent. -### 3. Restart `wp-env` +### 3. Restart `wp-env` with updates Restarting `wp-env` will restart the underlying Docker containers which can fix many issues. -To restart `wp-env`: +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. ```sh -$ wp-env stop -$ wp-env start +$ wp-env start --update ``` ### 4. Restart Docker @@ -156,16 +157,17 @@ $ wp-env clean all $ wp-env start ``` -### 6. Nuke everything and start again 🔥 +### 6. Destroy everything and start again 🔥 -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. +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. -To nuke everything: +To do so: **⚠️ WARNING: This will permanently delete any posts, pages, media, etc. in the local WordPress installation.** ```sh $ wp-env destroy +# This new instance is a fresh start with no existing data: $ wp-env start ``` @@ -211,10 +213,12 @@ wp-env start wp-env start --xdebug=profile,trace,debug ``` -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: +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: ```sh npm run wp-env start -- --xdebug +# Alternatively, use npx: +npx wp-env start --xdebug ``` 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. @@ -532,7 +536,7 @@ Additionally, the values referencing a URL include the specified port for the gi ### Examples -#### Latest production WordPress + current directory as a plugin +#### Latest stable WordPress + current directory as a plugin This is useful for plugin development. diff --git a/packages/env/lib/config/config.js b/packages/env/lib/config/config.js index 25b76d8d304b73..ae3d0a4ed364ae 100644 --- a/packages/env/lib/config/config.js +++ b/packages/env/lib/config/config.js @@ -15,6 +15,7 @@ const readRawConfigFile = require( './read-raw-config-file' ); const parseConfig = require( './parse-config' ); const { includeTestsPath, parseSourceString } = parseConfig; const md5 = require( '../md5' ); +const { getLatestWordPressVersion } = require( '../wordpress' ); /** * wp-env configuration. @@ -33,7 +34,7 @@ const md5 = require( '../md5' ); * Base-level config for any particular environment. (development/tests/etc) * * @typedef WPServiceConfig - * @property {?WPSource} coreSource The WordPress installation to load in the environment. + * @property {WPSource} coreSource The WordPress installation to load in the environment. * @property {WPSource[]} pluginSources Plugins to load in the environment. * @property {WPSource[]} themeSources Themes to load in the environment. * @property {number} port The port to use. @@ -68,9 +69,37 @@ module.exports = async function readConfig( configPath ) { md5( configPath ) ); + // The specified base configuration from .wp-env.json or from the local + // source type which was automatically detected. + const baseConfig = + ( await readRawConfigFile( '.wp-env.json', configPath ) ) || + ( await getDefaultBaseConfig( configPath ) ); + + // Overriden .wp-env.json on a per-user case. + const overrideConfig = + ( await readRawConfigFile( + '.wp-env.override.json', + configPath.replace( /\.wp-env\.json$/, '.wp-env.override.json' ) + ) ) || {}; + + const detectedLocalConfig = + Object.keys( { ...baseConfig, ...overrideConfig } ).length > 0; + + // If there is no local WordPress version, use the latest stable version from GitHub. + let coreRef; + if ( ! overrideConfig.core && ! baseConfig.core ) { + const wpVersion = await getLatestWordPressVersion(); + if ( ! wpVersion ) { + throw new ValidationError( + 'Could not find the latest WordPress version. There may be a network issue.' + ); + } + coreRef = `WordPress/WordPress#${ wpVersion }`; + } + // Default configuration which is overridden by .wp-env.json files. const defaultConfiguration = { - core: null, + core: coreRef, phpVersion: null, plugins: [], themes: [], @@ -96,22 +125,6 @@ module.exports = async function readConfig( configPath ) { }, }; - // The specified base configuration from .wp-env.json or from the local - // source type which was automatically detected. - const baseConfig = - ( await readRawConfigFile( '.wp-env.json', configPath ) ) || - ( await getDefaultBaseConfig( configPath ) ); - - // Overriden .wp-env.json on a per-user case. - const overrideConfig = - ( await readRawConfigFile( - '.wp-env.override.json', - configPath.replace( /\.wp-env\.json$/, '.wp-env.override.json' ) - ) ) || {}; - - const detectedLocalConfig = - Object.keys( { ...baseConfig, ...overrideConfig } ).length > 0; - // A quick validation before merging on a service by service level allows us // to check the root configuration options and provide more helpful errors. validateConfig( diff --git a/packages/env/lib/config/test/__snapshots__/config.js.snap b/packages/env/lib/config/test/__snapshots__/config.js.snap index 4ba5e9daacea15..ad0c18e4279de7 100644 --- a/packages/env/lib/config/test/__snapshots__/config.js.snap +++ b/packages/env/lib/config/test/__snapshots__/config.js.snap @@ -21,7 +21,6 @@ Object { "WP_TESTS_EMAIL": "admin@example.org", "WP_TESTS_TITLE": "Test Blog", }, - "coreSource": null, "mappings": Object {}, "phpVersion": null, "pluginSources": Array [], @@ -44,7 +43,6 @@ Object { "WP_TESTS_EMAIL": "admin@example.org", "WP_TESTS_TITLE": "Test Blog", }, - "coreSource": null, "mappings": Object {}, "phpVersion": null, "pluginSources": Array [], diff --git a/packages/env/lib/config/test/config.js b/packages/env/lib/config/test/config.js index fe76b87ec92325..1ca5a75d71bd79 100644 --- a/packages/env/lib/config/test/config.js +++ b/packages/env/lib/config/test/config.js @@ -19,6 +19,23 @@ jest.mock( 'fs', () => ( { }, } ) ); +// This mocks a small response with a format matching the stable-check API. +// It makes getLatestWordPressVersion resolve to "100.0.0". +jest.mock( 'got', () => + jest.fn( ( url ) => ( { + json: () => { + if ( url === 'https://api.wordpress.org/core/stable-check/1.0/' ) { + return Promise.resolve( { + '1.0': 'insecure', + '99.1.1': 'outdated', + '100.0.0': 'latest', + '100.0.1': 'fancy', + } ); + } + }, + } ) ) +); + jest.mock( '../detect-directory-type', () => jest.fn() ); describe( 'readConfig', () => { @@ -59,19 +76,38 @@ describe( 'readConfig', () => { ); detectDirectoryType.mockImplementation( () => 'core' ); const config = await readConfig( '.wp-env.json' ); - expect( config.env.development.coreSource ).not.toBeNull(); + expect( config.env.development.coreSource.type ).toBe( 'local' ); expect( config.env.tests.coreSource ).not.toBeNull(); expect( config.env.development.pluginSources ).toHaveLength( 0 ); expect( config.env.development.themeSources ).toHaveLength( 0 ); } ); + it( 'should use the most recent stable WordPress version for the default core source', async () => { + readFile.mockImplementation( () => + Promise.resolve( JSON.stringify( {} ) ) + ); + const config = await readConfig( '.wp-env.json' ); + + const expected = { + url: 'https://github.com/WordPress/WordPress.git', + type: 'git', + basename: 'WordPress', + ref: '100.0.0', // From the mock of https at the top of the file. + }; + + expect( config.env.development.coreSource ).toMatchObject( + expected + ); + expect( config.env.tests.coreSource ).toMatchObject( expected ); + } ); + it( 'should infer a plugin config when ran from a plugin directory', async () => { readFile.mockImplementation( () => Promise.reject( { code: 'ENOENT' } ) ); detectDirectoryType.mockImplementation( () => 'plugin' ); const config = await readConfig( '.wp-env.json' ); - expect( config.env.development.coreSource ).toBeNull(); + expect( config.env.development.coreSource.type ).toBe( 'git' ); expect( config.env.development.pluginSources ).toHaveLength( 1 ); expect( config.env.tests.pluginSources ).toHaveLength( 1 ); expect( config.env.development.themeSources ).toHaveLength( 0 ); @@ -83,8 +119,8 @@ describe( 'readConfig', () => { ); detectDirectoryType.mockImplementation( () => 'theme' ); const config = await readConfig( '.wp-env.json' ); - expect( config.env.development.coreSource ).toBeNull(); - expect( config.env.tests.coreSource ).toBeNull(); + expect( config.env.development.coreSource.type ).toBe( 'git' ); + expect( config.env.tests.coreSource.type ).toBe( 'git' ); expect( config.env.development.themeSources ).toHaveLength( 1 ); expect( config.env.tests.themeSources ).toHaveLength( 1 ); expect( config.env.development.pluginSources ).toHaveLength( 0 ); @@ -197,6 +233,11 @@ describe( 'readConfig', () => { // Remove generated values which are different on other machines. delete config.dockerComposeConfigPath; delete config.workDirectoryPath; + + // This encodes both the version of WordPress (which can change frequently) + // as well as the wp-env directory which is unique on every machine. + delete config.env.development.coreSource; + delete config.env.tests.coreSource; expect( config ).toMatchSnapshot(); } ); } ); diff --git a/packages/env/lib/wordpress.js b/packages/env/lib/wordpress.js index 62360b4c182e77..b1f54fb0e85d80 100644 --- a/packages/env/lib/wordpress.js +++ b/packages/env/lib/wordpress.js @@ -5,6 +5,7 @@ const dockerCompose = require( 'docker-compose' ); const util = require( 'util' ); const fs = require( 'fs' ).promises; const path = require( 'path' ); +const got = require( 'got' ); /** * Promisified dependencies @@ -246,11 +247,6 @@ async function copyCoreFiles( fromPath, toPath ) { * @return {string} The version of WordPress the source is for. */ async function readWordPressVersion( coreSource, spinner, debug ) { - // No source means they're using the bleeding edge. - if ( coreSource === null ) { - return null; - } - const versionFilePath = path.join( coreSource.path, 'wp-includes', @@ -275,6 +271,24 @@ async function readWordPressVersion( coreSource, spinner, debug ) { return versionMatch[ 1 ]; } +/** + * Returns the latest stable version of WordPress by requesting the stable-check + * endpoint on WordPress.org. + * + * @return {string} The latest stable version of WordPress, like "6.0.1" + */ +async function getLatestWordPressVersion() { + const versions = await got( + 'https://api.wordpress.org/core/stable-check/1.0/' + ).json(); + + for ( const [ version, status ] of Object.entries( versions ) ) { + if ( status === 'latest' ) { + return version; + } + } +} + module.exports = { hasSameCoreSource, checkDatabaseConnection, @@ -282,4 +296,5 @@ module.exports = { resetDatabase, setupWordPressDirectories, readWordPressVersion, + getLatestWordPressVersion, };