@wordpress/env 11.0.1-next.v.0 → 11.0.1-next.v.202602091733.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.
@@ -47,20 +47,6 @@ function isWPMajorMinorVersionLower( version, compareVersion ) {
47
47
  return versionNumber < compareVersionNumber;
48
48
  }
49
49
 
50
- /**
51
- * Checks a WordPress database connection. An error is thrown if the test is
52
- * unsuccessful.
53
- *
54
- * @param {WPConfig} config The wp-env config object.
55
- */
56
- async function checkDatabaseConnection( { dockerComposeConfigPath, debug } ) {
57
- await dockerCompose.run( 'cli', 'wp db check', {
58
- config: dockerComposeConfigPath,
59
- commandOptions: [ '--rm' ],
60
- log: debug,
61
- } );
62
- }
63
-
64
50
  /**
65
51
  * Configures WordPress for the given environment by installing WordPress,
66
52
  * activating all plugins, and activating the first theme. These steps are
@@ -316,7 +302,6 @@ async function copyCoreFiles( fromPath, toPath ) {
316
302
  }
317
303
 
318
304
  module.exports = {
319
- checkDatabaseConnection,
320
305
  configureWordPress,
321
306
  resetDatabase,
322
307
  setupWordPressDirectories,
@@ -12,6 +12,17 @@ class UnsupportedCommandError extends Error {
12
12
  }
13
13
  }
14
14
 
15
+ /**
16
+ * Error thrown when the environment has not been initialized.
17
+ */
18
+ class EnvironmentNotInitializedError extends Error {
19
+ constructor() {
20
+ super( 'Environment not initialized. Run `wp-env start` first.' );
21
+ this.name = 'EnvironmentNotInitializedError';
22
+ }
23
+ }
24
+
15
25
  module.exports = {
16
26
  UnsupportedCommandError,
27
+ EnvironmentNotInitializedError,
17
28
  };
@@ -1,17 +1,17 @@
1
1
  'use strict';
2
2
 
3
- /**
4
- * External dependencies
5
- */
6
- const { existsSync } = require( 'fs' );
7
- const path = require( 'path' );
8
-
9
3
  /**
10
4
  * Internal dependencies
11
5
  */
12
6
  const DockerRuntime = require( './docker' );
13
7
  const PlaygroundRuntime = require( './playground' );
14
- const { UnsupportedCommandError } = require( './errors' );
8
+ const {
9
+ UnsupportedCommandError,
10
+ EnvironmentNotInitializedError,
11
+ } = require( './errors' );
12
+ const { setCache, getCache } = require( '../cache' );
13
+
14
+ const RUNTIME_CACHE_KEY = 'runtime';
15
15
 
16
16
  const runtimes = {
17
17
  docker: DockerRuntime,
@@ -42,28 +42,50 @@ function getAvailableRuntimes() {
42
42
  }
43
43
 
44
44
  /**
45
- * Detect which runtime was used based on files in the work directory.
46
- * Returns 'playground' if playground-blueprint.json exists, otherwise 'docker'.
45
+ * Save the runtime type to the cache file.
46
+ * Called when start command initializes the environment.
47
+ *
48
+ * @param {string} runtimeName The runtime name ('docker' or 'playground').
49
+ * @param {string} workDirectoryPath Path to the wp-env work directory.
50
+ */
51
+ async function saveRuntime( runtimeName, workDirectoryPath ) {
52
+ await setCache( RUNTIME_CACHE_KEY, runtimeName, { workDirectoryPath } );
53
+ }
54
+
55
+ /**
56
+ * Get the saved runtime type from cache.
57
+ *
58
+ * @param {string} workDirectoryPath Path to the wp-env work directory.
59
+ * @return {Promise<string|undefined>} The saved runtime name, or undefined if not set.
60
+ */
61
+ async function getSavedRuntime( workDirectoryPath ) {
62
+ return await getCache( RUNTIME_CACHE_KEY, { workDirectoryPath } );
63
+ }
64
+
65
+ /**
66
+ * Detect which runtime was used by reading from the cache.
67
+ * Throws EnvironmentNotInitializedError if no runtime has been saved.
47
68
  *
48
69
  * @param {string} workDirectoryPath Path to the wp-env work directory.
49
- * @return {string} Runtime name ('docker' or 'playground').
70
+ * @return {Promise<string>} Runtime name ('docker' or 'playground').
71
+ * @throws {EnvironmentNotInitializedError} If environment not initialized.
50
72
  */
51
- function detectRuntime( workDirectoryPath ) {
52
- const playgroundBlueprintFile = path.join(
53
- workDirectoryPath,
54
- 'playground-blueprint.json'
55
- );
56
- if ( existsSync( playgroundBlueprintFile ) ) {
57
- return 'playground';
73
+ async function detectRuntime( workDirectoryPath ) {
74
+ const savedRuntime = await getSavedRuntime( workDirectoryPath );
75
+ if ( ! savedRuntime ) {
76
+ throw new EnvironmentNotInitializedError();
58
77
  }
59
- return 'docker';
78
+ return savedRuntime;
60
79
  }
61
80
 
62
81
  module.exports = {
63
82
  getRuntime,
64
83
  getAvailableRuntimes,
65
84
  detectRuntime,
85
+ saveRuntime,
86
+ getSavedRuntime,
66
87
  DockerRuntime,
67
88
  PlaygroundRuntime,
68
89
  UnsupportedCommandError,
90
+ EnvironmentNotInitializedError,
69
91
  };
@@ -107,21 +107,13 @@ function getMountArgs( config ) {
107
107
  }
108
108
 
109
109
  // Mount themes
110
+ // All theme types (local, git, zip) can be mounted after downloading/extraction
110
111
  for ( const theme of envConfig.themeSources || [] ) {
111
- if ( theme.type === 'local' || theme.type === 'git' ) {
112
- args.push(
113
- '--mount-dir',
114
- theme.path,
115
- `/wordpress/wp-content/themes/${ theme.basename }`
116
- );
117
- } else {
118
- throw new Error(
119
- `Theme source "${ theme.basename || theme.path }" of type "${
120
- theme.type
121
- }" ` +
122
- `is not supported with Playground runtime. Only local and git themes are supported.`
123
- );
124
- }
112
+ args.push(
113
+ '--mount-dir',
114
+ theme.path,
115
+ `/wordpress/wp-content/themes/${ theme.basename }`
116
+ );
125
117
  }
126
118
 
127
119
  // Mount custom mappings
@@ -75,6 +75,15 @@ class PlaygroundRuntime {
75
75
  return 'WARNING! This will remove the WordPress Playground environment and all local files.';
76
76
  }
77
77
 
78
+ /**
79
+ * Get the warning message for cleanup confirmation.
80
+ *
81
+ * @return {string} Warning message.
82
+ */
83
+ getCleanupWarningMessage() {
84
+ return 'WARNING! This will remove the WordPress Playground environment and all local files.';
85
+ }
86
+
78
87
  /**
79
88
  * Start the WordPress Playground environment.
80
89
  *
@@ -350,6 +359,25 @@ class PlaygroundRuntime {
350
359
  spinner.text = 'Removed WordPress Playground environment.';
351
360
  }
352
361
 
362
+ /**
363
+ * Cleanup the WordPress Playground environment.
364
+ *
365
+ * For Playground, cleanup is the same as destroy since there are no
366
+ * shared resources like Docker images to preserve.
367
+ *
368
+ * @param {Object} config The wp-env config object.
369
+ * @param {Object} options Cleanup options.
370
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
371
+ */
372
+ async cleanup( config, { spinner } ) {
373
+ await this.stop( config, { spinner } );
374
+
375
+ spinner.text = 'Removing local files.';
376
+ await rimraf( config.workDirectoryPath );
377
+
378
+ spinner.text = 'Cleaned up WordPress Playground environment.';
379
+ }
380
+
353
381
  /**
354
382
  * Run a command in the Playground environment.
355
383
  *
@@ -367,21 +395,21 @@ class PlaygroundRuntime {
367
395
  }
368
396
 
369
397
  /**
370
- * Clean/reset the WordPress database.
398
+ * Reset the WordPress database.
371
399
  *
372
400
  * @param {Object} config The wp-env config object.
373
- * @param {Object} options Clean options.
401
+ * @param {Object} options Reset options.
374
402
  * @param {Object} options.spinner A CLI spinner which indicates progress.
375
403
  * @param {boolean} options.debug True if debug mode is enabled.
376
404
  */
377
405
  async clean( config, { spinner, debug } ) {
378
- spinner.text = 'Cleaning WordPress Playground environment.';
406
+ spinner.text = 'Resetting WordPress Playground environment.';
379
407
 
380
408
  // For Playground, we restart the server to reset the database
381
409
  await this.stop( config, { spinner } );
382
410
  await this.start( config, { spinner, debug } );
383
411
 
384
- spinner.text = 'Cleaned WordPress Playground environment.';
412
+ spinner.text = 'Reset WordPress Playground environment.';
385
413
  }
386
414
 
387
415
  /**
@@ -131,4 +131,87 @@ describe( 'buildDockerComposeConfig', () => {
131
131
  expect( dockerConfig.volumes.wordpress ).toBe( undefined );
132
132
  expect( dockerConfig.volumes[ 'tests-wordpress' ] ).toBe( undefined );
133
133
  } );
134
+
135
+ it( 'should add healthcheck to mysql services', () => {
136
+ const config = buildDockerComposeConfig( {
137
+ workDirectoryPath: '/some/path',
138
+ env: {
139
+ development: {
140
+ port: 8888,
141
+ mysqlPort: 3306,
142
+ coreSource: null,
143
+ pluginSources: [],
144
+ themeSources: [],
145
+ mappings: {},
146
+ },
147
+ tests: {
148
+ port: 8889,
149
+ mysqlPort: 3307,
150
+ coreSource: null,
151
+ pluginSources: [],
152
+ themeSources: [],
153
+ mappings: {},
154
+ },
155
+ },
156
+ } );
157
+
158
+ expect( config.services.mysql.healthcheck ).toBeDefined();
159
+ expect( config.services.mysql.healthcheck.test ).toEqual( [
160
+ 'CMD',
161
+ 'healthcheck.sh',
162
+ '--connect',
163
+ '--innodb_initialized',
164
+ ] );
165
+ expect( config.services.mysql.healthcheck.interval ).toBe( '5s' );
166
+ expect( config.services.mysql.healthcheck.timeout ).toBe( '10s' );
167
+ expect( config.services.mysql.healthcheck.retries ).toBe( 12 );
168
+ expect( config.services.mysql.healthcheck.start_period ).toBe( '60s' );
169
+
170
+ // Verify MARIADB_AUTO_UPGRADE is set for existing installations
171
+ expect( config.services.mysql.environment.MARIADB_AUTO_UPGRADE ).toBe(
172
+ '1'
173
+ );
174
+
175
+ expect( config.services[ 'tests-mysql' ].healthcheck ).toBeDefined();
176
+ expect( config.services[ 'tests-mysql' ].healthcheck.test ).toEqual( [
177
+ 'CMD',
178
+ 'healthcheck.sh',
179
+ '--connect',
180
+ '--innodb_initialized',
181
+ ] );
182
+ expect(
183
+ config.services[ 'tests-mysql' ].environment.MARIADB_AUTO_UPGRADE
184
+ ).toBe( '1' );
185
+ } );
186
+
187
+ it( 'should use service_healthy condition for WordPress depends_on', () => {
188
+ const config = buildDockerComposeConfig( {
189
+ workDirectoryPath: '/some/path',
190
+ env: {
191
+ development: {
192
+ port: 8888,
193
+ mysqlPort: 3306,
194
+ coreSource: null,
195
+ pluginSources: [],
196
+ themeSources: [],
197
+ mappings: {},
198
+ },
199
+ tests: {
200
+ port: 8889,
201
+ mysqlPort: 3307,
202
+ coreSource: null,
203
+ pluginSources: [],
204
+ themeSources: [],
205
+ mappings: {},
206
+ },
207
+ },
208
+ } );
209
+
210
+ expect( config.services.wordpress.depends_on ).toEqual( {
211
+ mysql: { condition: 'service_healthy' },
212
+ } );
213
+ expect( config.services[ 'tests-wordpress' ].depends_on ).toEqual( {
214
+ 'tests-mysql': { condition: 'service_healthy' },
215
+ } );
216
+ } );
134
217
  } );
package/lib/test/cli.js CHANGED
@@ -19,8 +19,11 @@ jest.mock( '../env', () => {
19
19
  return {
20
20
  start: jest.fn( Promise.resolve.bind( Promise ) ),
21
21
  stop: jest.fn( Promise.resolve.bind( Promise ) ),
22
+ reset: jest.fn( Promise.resolve.bind( Promise ) ),
22
23
  clean: jest.fn( Promise.resolve.bind( Promise ) ),
23
24
  run: jest.fn( Promise.resolve.bind( Promise ) ),
25
+ destroy: jest.fn( Promise.resolve.bind( Promise ) ),
26
+ cleanup: jest.fn( Promise.resolve.bind( Promise ) ),
24
27
  ValidationError: actual.ValidationError,
25
28
  LifecycleScriptError: actual.LifecycleScriptError,
26
29
  };
@@ -41,26 +44,33 @@ describe( 'env cli', () => {
41
44
  expect( spinner.text ).toBe( '' );
42
45
  } );
43
46
 
44
- it( 'parses clean commands for the default environment.', () => {
45
- cli().parse( [ 'clean' ] );
46
- const { environment, spinner } = env.clean.mock.calls[ 0 ][ 0 ];
47
+ it( 'parses reset commands for the default environment.', () => {
48
+ cli().parse( [ 'reset' ] );
49
+ const { environment, spinner } = env.reset.mock.calls[ 0 ][ 0 ];
47
50
  expect( environment ).toBe( 'tests' );
48
51
  expect( spinner.text ).toBe( '' );
49
52
  } );
50
- it( 'parses clean commands for all environments.', () => {
51
- cli().parse( [ 'clean', 'all' ] );
52
- const { environment, spinner } = env.clean.mock.calls[ 0 ][ 0 ];
53
+ it( 'parses reset commands for all environments.', () => {
54
+ cli().parse( [ 'reset', 'all' ] );
55
+ const { environment, spinner } = env.reset.mock.calls[ 0 ][ 0 ];
53
56
  expect( environment ).toBe( 'all' );
54
57
  expect( spinner.text ).toBe( '' );
55
58
  } );
56
- it( 'parses clean commands for the development environment.', () => {
57
- cli().parse( [ 'clean', 'development' ] );
58
- const { environment, spinner } = env.clean.mock.calls[ 0 ][ 0 ];
59
+ it( 'parses reset commands for the development environment.', () => {
60
+ cli().parse( [ 'reset', 'development' ] );
61
+ const { environment, spinner } = env.reset.mock.calls[ 0 ][ 0 ];
59
62
  expect( environment ).toBe( 'development' );
60
63
  expect( spinner.text ).toBe( '' );
61
64
  } );
62
- it( 'parses clean commands for the tests environment.', () => {
63
- cli().parse( [ 'clean', 'tests' ] );
65
+ it( 'parses reset commands for the tests environment.', () => {
66
+ cli().parse( [ 'reset', 'tests' ] );
67
+ const { environment, spinner } = env.reset.mock.calls[ 0 ][ 0 ];
68
+ expect( environment ).toBe( 'tests' );
69
+ expect( spinner.text ).toBe( '' );
70
+ } );
71
+
72
+ it( 'parses clean (deprecated) commands for the default environment.', () => {
73
+ cli().parse( [ 'clean' ] );
64
74
  const { environment, spinner } = env.clean.mock.calls[ 0 ][ 0 ];
65
75
  expect( environment ).toBe( 'tests' );
66
76
  expect( spinner.text ).toBe( '' );
@@ -81,6 +91,32 @@ describe( 'env cli', () => {
81
91
  expect( spinner.text ).toBe( '' );
82
92
  } );
83
93
 
94
+ it( 'parses destroy commands.', () => {
95
+ cli().parse( [ 'destroy' ] );
96
+ const { spinner, scripts, force } = env.destroy.mock.calls[ 0 ][ 0 ];
97
+ expect( spinner.text ).toBe( '' );
98
+ expect( scripts ).toBe( true );
99
+ expect( force ).toBe( false );
100
+ } );
101
+ it( 'parses destroy commands with --force flag.', () => {
102
+ cli().parse( [ 'destroy', '--force' ] );
103
+ const { force } = env.destroy.mock.calls[ 0 ][ 0 ];
104
+ expect( force ).toBe( true );
105
+ } );
106
+
107
+ it( 'parses cleanup commands.', () => {
108
+ cli().parse( [ 'cleanup' ] );
109
+ const { spinner, scripts, force } = env.cleanup.mock.calls[ 0 ][ 0 ];
110
+ expect( spinner.text ).toBe( '' );
111
+ expect( scripts ).toBe( true );
112
+ expect( force ).toBe( false );
113
+ } );
114
+ it( 'parses cleanup commands with --force flag.', () => {
115
+ cli().parse( [ 'cleanup', '--force' ] );
116
+ const { force } = env.cleanup.mock.calls[ 0 ][ 0 ];
117
+ expect( force ).toBe( true );
118
+ } );
119
+
84
120
  it( 'handles successful commands with messages.', async () => {
85
121
  env.start.mockResolvedValueOnce( 'success message' );
86
122
  cli().parse( [ 'start' ] );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/env",
3
- "version": "11.0.1-next.v.0+5aba098fc",
3
+ "version": "11.0.1-next.v.202602091733.0+daa0b19c4",
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",
@@ -64,5 +64,5 @@
64
64
  "scripts": {
65
65
  "test": "echo \"Error: run tests from root\" && exit 1"
66
66
  },
67
- "gitHead": "d730f9e00f5462d1b9d2660632850f5f43ccff44"
67
+ "gitHead": "74f59922b25e30904319373dda91bf8e81f3544e"
68
68
  }