@wordpress/env 10.38.1-next.v.0 → 10.39.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/README.md CHANGED
@@ -20,10 +20,39 @@ The database credentials are: user `root`, password `password`. For a comprehens
20
20
 
21
21
  `wp-env` relies on a few commonly used developer tools:
22
22
 
23
- - **Docker**. `wp-env` is powered by Docker. There are instructions available for installing Docker on [Windows](https://docs.docker.com/desktop/install/windows-install/) (we recommend the WSL2 backend), [macOS](https://docs.docker.com/docker-for-mac/install/), and [Linux](https://docs.docker.com/desktop/install/linux-install/).
23
+ - **Docker**. `wp-env` is powered by Docker by default. There are instructions available for installing Docker on [Windows](https://docs.docker.com/desktop/install/windows-install/) (we recommend the WSL2 backend), [macOS](https://docs.docker.com/docker-for-mac/install/), and [Linux](https://docs.docker.com/desktop/install/linux-install/).
24
24
  - **Node.js**. `wp-env` is written as a Node script. We recommend using a Node version manager like [nvm](https://github.com/nvm-sh/nvm) to install the latest LTS version. Alternatively, you can [download it directly here](https://nodejs.org/en/download).
25
25
  - **git**. Git is used for downloading software from source control, such as WordPress, plugins, and themes. [You can find the installation instructions here.](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
26
26
 
27
+ ## Experimental: WordPress Playground Runtime
28
+
29
+ `wp-env` now supports an experimental alternative runtime using [WordPress Playground](https://wordpress.github.io/wordpress-playground/). Playground runs WordPress entirely in WebAssembly, eliminating the need for Docker.
30
+
31
+ To use the Playground runtime:
32
+
33
+ ```sh
34
+ $ wp-env start --runtime=playground
35
+ ```
36
+
37
+ ### Playground vs Docker
38
+
39
+ | Feature | Docker | Playground |
40
+ |---------|--------|------------|
41
+ | Requires Docker | Yes | No |
42
+ | Test environment | Yes | No |
43
+ | Xdebug | Yes | Yes |
44
+ | SPX profiling | Yes | No |
45
+ | phpMyAdmin | Yes | No |
46
+ | MySQL database | Yes | No (SQLite) |
47
+ | Multisite | Yes | Yes |
48
+ | Custom PHP version | Yes | Yes |
49
+ | Plugin/theme mounting | Yes | Yes |
50
+ | `wp-env run` command | Yes | No |
51
+
52
+ The Playground runtime is ideal for quick testing or environments where Docker is unavailable. However, it lacks some features available in the Docker runtime, such as the tests environment and the `run` command for executing arbitrary commands.
53
+
54
+ Once started with a runtime, wp-env will automatically detect and use the same runtime for subsequent commands (`stop`, `destroy`, etc.) until the environment is destroyed.
55
+
27
56
  ## Installation
28
57
 
29
58
  ### Installation as a global package
@@ -284,6 +313,9 @@ Options:
284
313
  --debug Enable debug output. [boolean] [default: false]
285
314
  --update Download source updates and apply WordPress configuration.
286
315
  [boolean] [default: false]
316
+ --runtime Select the runtime to use. "docker" uses Docker containers,
317
+ "playground" uses WordPress Playground (experimental).
318
+ [string] [choices: "docker", "playground"]
287
319
  --xdebug Enables Xdebug. If not passed, Xdebug is turned off. If no modes
288
320
  are set, uses "debug". You may set multiple Xdebug modes by passing
289
321
  them in a comma-separated list: `--xdebug=develop,coverage`. See
package/lib/cli.js CHANGED
@@ -6,7 +6,6 @@ const chalk = require( 'chalk' );
6
6
  const ora = require( 'ora' );
7
7
  const yargs = require( 'yargs' );
8
8
  const terminalLink = require( 'terminal-link' );
9
- const { execSync } = require( 'child_process' );
10
9
 
11
10
  /**
12
11
  * Internal dependencies
@@ -16,9 +15,10 @@ const env = require( './env' );
16
15
  const parseXdebugMode = require( './parse-xdebug-mode' );
17
16
  const parseSpxMode = require( './parse-spx-mode' );
18
17
  const {
19
- RUN_CONTAINERS,
20
- validateRunContainer,
21
- } = require( './validate-run-container' );
18
+ getAvailableRuntimes,
19
+ getRuntime,
20
+ UnsupportedCommandError,
21
+ } = require( './runtime' );
22
22
 
23
23
  // Colors.
24
24
  const boldWhite = chalk.bold.white;
@@ -45,7 +45,11 @@ const withSpinner =
45
45
  process.exit( 0 );
46
46
  },
47
47
  ( error ) => {
48
- if (
48
+ if ( error instanceof UnsupportedCommandError ) {
49
+ // Error is an unsupported command in the current runtime.
50
+ spinner.fail( error.message );
51
+ process.exit( 1 );
52
+ } else if (
49
53
  error instanceof env.ValidationError ||
50
54
  error instanceof env.LifecycleScriptError
51
55
  ) {
@@ -87,16 +91,6 @@ const withSpinner =
87
91
  };
88
92
 
89
93
  module.exports = function cli() {
90
- // Do nothing if Docker is unavailable.
91
- try {
92
- execSync( 'docker info', { stdio: 'ignore' } );
93
- } catch {
94
- console.error(
95
- chalk.red( 'Could not connect to Docker. Is it running?' )
96
- );
97
- process.exit( 1 );
98
- }
99
-
100
94
  yargs.usage( wpPrimary( '$0 <command>' ) );
101
95
  yargs.option( 'debug', {
102
96
  type: 'boolean',
@@ -150,6 +144,13 @@ module.exports = function cli() {
150
144
  describe: 'Execute any configured lifecycle scripts.',
151
145
  default: true,
152
146
  } );
147
+ args.option( 'runtime', {
148
+ type: 'string',
149
+ describe:
150
+ 'The runtime environment to use. "docker" uses Docker containers, "playground" uses WordPress Playground (experimental).',
151
+ choices: getAvailableRuntimes(),
152
+ default: 'docker',
153
+ } );
153
154
  },
154
155
  withSpinner( env.start )
155
156
  );
@@ -201,6 +202,10 @@ module.exports = function cli() {
201
202
  '$0 logs --no-watch --environment=tests',
202
203
  'Displays the latest logs for the e2e test environment without watching.'
203
204
  );
205
+ // Get run containers from Docker runtime (run command is Docker-only for now)
206
+ const dockerRuntime = getRuntime( 'docker' );
207
+ const runContainers = dockerRuntime.getRunContainers();
208
+
204
209
  yargs.command(
205
210
  'run <container> [command...]',
206
211
  'Runs an arbitrary command in one of the underlying Docker containers. A double dash can be used to pass arguments to the container without parsing them. This is necessary if you are using an option that is defined below. You can use `bash` to open a shell session and both `composer` and `phpunit` are available in all WordPress and CLI containers. WP-CLI is also available in the CLI containers.',
@@ -216,8 +221,7 @@ module.exports = function cli() {
216
221
  type: 'string',
217
222
  describe:
218
223
  'The underlying Docker service to run the command on.',
219
- choices: RUN_CONTAINERS,
220
- coerce: validateRunContainer,
224
+ choices: runContainers,
221
225
  } );
222
226
  args.positional( 'command', {
223
227
  type: 'array',
@@ -2,14 +2,14 @@
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' );
11
- const { configureWordPress, resetDatabase } = require( '../wordpress' );
12
10
  const { executeLifecycleScript } = require( '../execute-lifecycle-script' );
11
+ const { loadConfig } = require( '../config' );
12
+ const { getRuntime, detectRuntime } = require( '../runtime' );
13
13
 
14
14
  /**
15
15
  * @typedef {import('../wordpress').WPEnvironment} WPEnvironment
@@ -31,43 +31,12 @@ module.exports = async function clean( {
31
31
  scripts,
32
32
  debug,
33
33
  } ) {
34
- const config = await initConfig( { spinner, debug } );
34
+ const config = await loadConfig( path.resolve( '.' ) );
35
+ const runtime = getRuntime( detectRuntime( config.workDirectoryPath ) );
35
36
 
36
- const description = `${ environment } environment${
37
- environment === 'all' ? 's' : ''
38
- }`;
39
- spinner.text = `Cleaning ${ description }.`;
40
-
41
- const tasks = [];
42
-
43
- // Start the database first to avoid race conditions where all tasks create
44
- // different docker networks with the same name.
45
- await dockerCompose.upOne( 'mysql', {
46
- config: config.dockerComposeConfigPath,
47
- log: config.debug,
48
- } );
49
-
50
- if ( environment === 'all' || environment === 'development' ) {
51
- tasks.push(
52
- resetDatabase( 'development', config )
53
- .then( () => configureWordPress( 'development', config ) )
54
- .catch( () => {} )
55
- );
56
- }
57
-
58
- if ( environment === 'all' || environment === 'tests' ) {
59
- tasks.push(
60
- resetDatabase( 'tests', config )
61
- .then( () => configureWordPress( 'tests', config ) )
62
- .catch( () => {} )
63
- );
64
- }
65
-
66
- await Promise.all( tasks );
37
+ await runtime.clean( config, { environment, spinner, debug } );
67
38
 
68
39
  if ( scripts ) {
69
40
  await executeLifecycleScript( 'afterClean', config, spinner );
70
41
  }
71
-
72
- spinner.text = `Cleaned ${ description }.`;
73
42
  };
@@ -2,21 +2,16 @@
2
2
  /**
3
3
  * External dependencies
4
4
  */
5
- const { v2: dockerCompose } = require( 'docker-compose' );
6
5
  const fs = require( 'fs' ).promises;
7
6
  const path = require( 'path' );
8
7
  const { confirm } = require( '@inquirer/prompts' );
9
8
 
10
- /**
11
- * Promisified dependencies
12
- */
13
- const { rimraf } = require( 'rimraf' );
14
-
15
9
  /**
16
10
  * Internal dependencies
17
11
  */
18
12
  const { loadConfig } = require( '../config' );
19
13
  const { executeLifecycleScript } = require( '../execute-lifecycle-script' );
14
+ const { getRuntime, detectRuntime } = require( '../runtime' );
20
15
 
21
16
  /**
22
17
  * Destroy the development server.
@@ -36,9 +31,9 @@ module.exports = async function destroy( { spinner, scripts, debug } ) {
36
31
  return;
37
32
  }
38
33
 
39
- spinner.info(
40
- 'WARNING! This will remove Docker containers, volumes, networks, and images associated with the WordPress instance.'
41
- );
34
+ const runtime = getRuntime( detectRuntime( config.workDirectoryPath ) );
35
+
36
+ spinner.info( runtime.getDestroyWarningMessage() );
42
37
 
43
38
  let yesDelete = false;
44
39
  try {
@@ -61,24 +56,9 @@ module.exports = async function destroy( { spinner, scripts, debug } ) {
61
56
  return;
62
57
  }
63
58
 
64
- spinner.text = 'Removing docker images, volumes, and networks.';
65
-
66
- await dockerCompose.down( {
67
- config: config.dockerComposeConfigPath,
68
- commandOptions: [ '--volumes', '--remove-orphans', '--rmi', 'all' ],
69
- log: debug,
70
- } );
71
-
72
- spinner.text = 'Removing local files.';
73
- // Note: there is a race condition where docker compose actually hasn't finished
74
- // by this point, which causes rimraf to fail. We need to wait at least 2.5-5s,
75
- // but using 10s in case it's dependant on the machine.
76
- await new Promise( ( resolve ) => setTimeout( resolve, 10000 ) );
77
- await rimraf( config.workDirectoryPath );
59
+ await runtime.destroy( config, { spinner, debug } );
78
60
 
79
61
  if ( scripts ) {
80
62
  await executeLifecycleScript( 'afterDestroy', config, spinner );
81
63
  }
82
-
83
- spinner.text = 'Removed WordPress environment.';
84
64
  };
@@ -1,21 +1,33 @@
1
1
  'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const path = require( 'path' );
6
+ const { existsSync } = require( 'fs' );
7
+
2
8
  /**
3
9
  * Internal dependencies
4
10
  */
5
- const initConfig = require( '../init-config' );
11
+ const { loadConfig } = require( '../config' );
6
12
 
7
13
  /**
8
14
  * Logs the path to where wp-env files are installed.
9
15
  *
10
- * @param {Object} options
11
- * @param {Object} options.spinner
12
- * @param {boolean} options.debug
16
+ * @param {Object} options
17
+ * @param {Object} options.spinner
13
18
  */
14
- module.exports = async function installPath( { spinner, debug } ) {
19
+ module.exports = async function installPath( { spinner } ) {
15
20
  // Stop the spinner so that stdout is not polluted.
16
21
  spinner.stop();
17
- // initConfig will fail if wp-env start has not yet been called, so that
18
- // edge case is handled.
19
- const { workDirectoryPath } = await initConfig( { spinner, debug } );
20
- console.log( workDirectoryPath );
22
+
23
+ const config = await loadConfig( path.resolve( '.' ) );
24
+
25
+ if ( ! existsSync( config.workDirectoryPath ) ) {
26
+ console.error(
27
+ 'wp-env has not yet been initialized. Please run `wp-env start` first.'
28
+ );
29
+ process.exit( 1 );
30
+ }
31
+
32
+ console.log( config.workDirectoryPath );
21
33
  };
@@ -2,12 +2,13 @@
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
  * Displays the Docker & PHP logs on the given environment.
@@ -19,59 +20,7 @@ const initConfig = require( '../init-config' );
19
20
  * @param {boolean} options.debug True if debug mode is enabled.
20
21
  */
21
22
  module.exports = async function logs( { environment, watch, spinner, debug } ) {
22
- const config = await initConfig( { spinner, debug } );
23
-
24
- // If we show text while watching the logs, it will continue showing up every
25
- // few lines in the logs as they happen, which isn't a good look. So only
26
- // show the message if we are not watching the logs.
27
- if ( ! watch ) {
28
- spinner.text = `Showing logs for the ${ environment } environment.`;
29
- }
30
-
31
- const servicesToWatch =
32
- environment === 'all'
33
- ? [ 'tests-wordpress', 'wordpress' ]
34
- : [ environment === 'tests' ? 'tests-wordpress' : 'wordpress' ];
35
-
36
- const output = await Promise.all( [
37
- ...servicesToWatch.map( ( service ) =>
38
- dockerCompose.logs( service, {
39
- config: config.dockerComposeConfigPath,
40
- log: watch, // Must log inline if we are watching the log output.
41
- commandOptions: watch ? [ '--follow' ] : [],
42
- } )
43
- ),
44
- ] );
45
-
46
- // Combine the results from each docker output.
47
- const result = output.reduce(
48
- ( acc, current ) => {
49
- if ( current.out ) {
50
- acc.out = acc.out.concat( current.out );
51
- }
52
- if ( current.err ) {
53
- acc.err = acc.err.concat( current.err );
54
- }
55
- if ( current.exitCode !== 0 ) {
56
- acc.hasNon0ExitCode = true;
57
- }
58
- return acc;
59
- },
60
- { out: '', err: '', hasNon0ExitCode: false }
61
- );
62
-
63
- if ( result.out.length ) {
64
- console.log(
65
- process.stdout.isTTY ? `\n\n${ result.out }\n\n` : result.out
66
- );
67
- } else if ( result.err.length ) {
68
- console.error(
69
- process.stdout.isTTY ? `\n\n${ result.err }\n\n` : result.err
70
- );
71
- if ( result.hasNon0ExitCode ) {
72
- throw result.err;
73
- }
74
- }
75
-
76
- spinner.text = 'Finished showing logs.';
23
+ const config = await loadConfig( path.resolve( '.' ) );
24
+ const runtime = getRuntime( detectRuntime( config.workDirectoryPath ) );
25
+ await runtime.logs( config, { environment, watch, spinner, debug } );
77
26
  };
@@ -2,18 +2,13 @@
2
2
  /**
3
3
  * External dependencies
4
4
  */
5
- const { spawn } = require( 'child_process' );
6
5
  const path = require( 'path' );
7
6
 
8
7
  /**
9
8
  * Internal dependencies
10
9
  */
11
- const initConfig = require( '../init-config' );
12
- const getHostUser = require( '../get-host-user' );
13
-
14
- /**
15
- * @typedef {import('../config').WPConfig} WPConfig
16
- */
10
+ const { loadConfig } = require( '../config' );
11
+ const { getRuntime, detectRuntime } = require( '../runtime' );
17
12
 
18
13
  /**
19
14
  * Runs an arbitrary command on the given Docker container.
@@ -34,7 +29,8 @@ module.exports = async function run( {
34
29
  spinner,
35
30
  debug,
36
31
  } ) {
37
- const config = await initConfig( { spinner, debug } );
32
+ const config = await loadConfig( path.resolve( '.' ) );
33
+ const runtime = getRuntime( detectRuntime( config.workDirectoryPath ) );
38
34
 
39
35
  // Include any double dashed arguments in the command so that we can pass them to Docker.
40
36
  // This lets users pass options that the command defines without them being parsed.
@@ -42,106 +38,5 @@ module.exports = async function run( {
42
38
  command.push( ...doubleDashedArgs );
43
39
  }
44
40
 
45
- // Shows a contextual tip for the given command.
46
- const joinedCommand = command.join( ' ' );
47
- showCommandTips( joinedCommand, container, spinner );
48
-
49
- await spawnCommandDirectly( config, container, command, envCwd, spinner );
50
-
51
- spinner.text = `Ran \`${ joinedCommand }\` in '${ container }'.`;
41
+ await runtime.run( config, { container, command, envCwd, spinner, debug } );
52
42
  };
53
-
54
- /**
55
- * Runs an arbitrary command on the given Docker container.
56
- *
57
- * @param {WPConfig} config The wp-env configuration.
58
- * @param {string} container The Docker container to run the command on.
59
- * @param {string[]} command The command to run.
60
- * @param {string} envCwd The working directory for the command to be executed from.
61
- * @param {Object} spinner A CLI spinner which indicates progress.
62
- */
63
- function spawnCommandDirectly( config, container, command, envCwd, spinner ) {
64
- // Both the `wordpress` and `tests-wordpress` containers have the host's
65
- // user so that they can maintain ownership parity with the host OS.
66
- // We should run any commands as that user so that they are able
67
- // to interact with the files mounted from the host.
68
- const hostUser = getHostUser();
69
-
70
- // Since Docker requires absolute paths, we should resolve the input to a POSIX path.
71
- // This is needed because Windows resolves relative paths from the C: drive.
72
- envCwd = path.posix.resolve(
73
- // Not all containers have the same starting working directory.
74
- container === 'mysql' || container === 'tests-mysql'
75
- ? '/'
76
- : '/var/www/html',
77
- // Remove spaces and single quotes from both ends of the path.
78
- // This is needed because Windows treats single quotes as a literal character.
79
- envCwd.trim().replace( /^'|'$/g, '' )
80
- );
81
-
82
- const composeCommand = [
83
- 'compose',
84
- '-f',
85
- config.dockerComposeConfigPath,
86
- 'exec',
87
- '-w',
88
- envCwd,
89
- '--user',
90
- hostUser.fullUser,
91
- ];
92
-
93
- if ( ! process.stdout.isTTY ) {
94
- composeCommand.push( '-T' );
95
- }
96
-
97
- composeCommand.push( container, ...command );
98
-
99
- return new Promise( ( resolve, reject ) => {
100
- // Note: since the npm docker-compose package uses the -T option, we
101
- // cannot use it to spawn an interactive command. Thus, we run docker-
102
- // compose on the CLI directly.
103
- const childProc = spawn(
104
- 'docker',
105
- composeCommand,
106
- { stdio: 'inherit' },
107
- spinner
108
- );
109
- childProc.on( 'error', reject );
110
- childProc.on( 'exit', ( code ) => {
111
- // Code 130 is set if the user tries to exit with ctrl-c before using
112
- // ctrl-d (so it is not an error which should fail the script.)
113
- if ( code === 0 || code === 130 ) {
114
- resolve();
115
- } else {
116
- reject( `Command failed with exit code ${ code }` );
117
- }
118
- } );
119
- } );
120
- }
121
-
122
- /**
123
- * This shows a contextual tip for the command being run. Certain commands (like
124
- * bash) may have weird behavior (exit with ctrl-d instead of ctrl-c or ctrl-z),
125
- * so we want the user to have that information without having to ask someone.
126
- *
127
- * @param {string} joinedCommand The command for which to show a tip joined by spaces.
128
- * @param {string} container The container the command will be run on.
129
- * @param {Object} spinner A spinner object to show progress.
130
- */
131
- function showCommandTips( joinedCommand, container, spinner ) {
132
- if ( ! joinedCommand.length ) {
133
- return;
134
- }
135
-
136
- const tip = `Starting '${ joinedCommand }' on the ${ container } container. ${ ( () => {
137
- switch ( joinedCommand ) {
138
- case 'bash':
139
- return 'Exit bash with ctrl-d.';
140
- case 'wp shell':
141
- return 'Exit the WordPress shell with ctrl-c.';
142
- default:
143
- return '';
144
- }
145
- } )() }\n`;
146
- spinner.info( tip );
147
- }