@wordpress/env 7.0.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,6 @@ const {
14
14
  } = require( './parse-source-string' );
15
15
  const {
16
16
  ValidationError,
17
- checkString,
18
17
  checkPort,
19
18
  checkStringArray,
20
19
  checkObjectWithValues,
@@ -34,9 +33,13 @@ const mergeConfigs = require( './merge-configs' );
34
33
  * The root configuration options.
35
34
  *
36
35
  * @typedef WPRootConfigOptions
37
- * @property {number} port The port to use in the development environment.
38
- * @property {number} testsPort The port to use in the tests environment.
39
- * @property {string|null} afterSetup The command(s) to run after configuring WordPress on start and clean.
36
+ * @property {number} port The port to use in the development environment.
37
+ * @property {number} testsPort The port to use in the tests environment.
38
+ * @property {Object.<string, string|null>} lifecycleScripts The scripts to run at certain points in the command lifecycle.
39
+ * @property {Object.<string, string|null>} lifecycleScripts.afterStart The script to run after the "start" command has completed.
40
+ * @property {Object.<string, string|null>} lifecycleScripts.afterClean The script to run after the "clean" command has completed.
41
+ * @property {Object.<string, string|null>} lifecycleScripts.afterDestroy The script to run after the "destroy" command has completed.
42
+ * @property {Object.<string, WPEnvironmentConfig>} env The environment-specific configuration options.
40
43
  */
41
44
 
42
45
  /**
@@ -69,6 +72,34 @@ const mergeConfigs = require( './merge-configs' );
69
72
  * @property {string} basename Name that identifies the WordPress installation, plugin or theme.
70
73
  */
71
74
 
75
+ /**
76
+ * An object containing all of the default configuration options for environment-specific configurations.
77
+ * Unless otherwise set at the root-level or the environment-level, these are the values that will be
78
+ * parsed into the environment. This is useful for tracking known configuration options since these
79
+ * are the only configuration options that can be set in each environment.
80
+ */
81
+ const DEFAULT_ENVIRONMENT_CONFIG = {
82
+ core: null,
83
+ phpVersion: null,
84
+ plugins: [],
85
+ themes: [],
86
+ port: 8888,
87
+ testsPort: 8889,
88
+ mappings: {},
89
+ config: {
90
+ FS_METHOD: 'direct',
91
+ WP_DEBUG: true,
92
+ SCRIPT_DEBUG: true,
93
+ WP_ENVIRONMENT_TYPE: 'local',
94
+ WP_PHP_BINARY: 'php',
95
+ WP_TESTS_EMAIL: 'admin@example.org',
96
+ WP_TESTS_TITLE: 'Test Blog',
97
+ WP_TESTS_DOMAIN: 'localhost',
98
+ WP_SITEURL: 'http://localhost',
99
+ WP_HOME: 'http://localhost',
100
+ },
101
+ };
102
+
72
103
  /**
73
104
  * Given a directory, this parses any relevant config files and
74
105
  * constructs an object in the format used internally.
@@ -164,9 +195,7 @@ async function getDefaultConfig(
164
195
  configDirectoryPath,
165
196
  { shouldInferType, cacheDirectoryPath }
166
197
  ) {
167
- // Our default config should try to infer what type of project
168
- // this is in order to automatically map the current directory.
169
- const detectedType = shouldInferType
198
+ const detectedDirectoryType = shouldInferType
170
199
  ? await detectDirectoryType( configDirectoryPath )
171
200
  : null;
172
201
 
@@ -175,29 +204,40 @@ async function getDefaultConfig(
175
204
  // config objects easier because once merged we don't need to
176
205
  // verify that a given option exists before using it.
177
206
  const rawConfig = {
178
- core: detectedType === 'core' ? '.' : null,
179
- phpVersion: null,
180
- plugins: detectedType === 'plugin' ? [ '.' ] : [],
181
- themes: detectedType === 'theme' ? [ '.' ] : [],
182
- port: 8888,
183
- testsPort: 8889,
184
- mappings: {},
185
- config: {
186
- WP_DEBUG: true,
187
- SCRIPT_DEBUG: true,
188
- WP_ENVIRONMENT_TYPE: 'local',
189
- WP_PHP_BINARY: 'php',
190
- WP_TESTS_EMAIL: 'admin@example.org',
191
- WP_TESTS_TITLE: 'Test Blog',
192
- WP_TESTS_DOMAIN: 'localhost',
193
- WP_SITEURL: 'http://localhost',
194
- WP_HOME: 'http://localhost',
207
+ // Since the root config is the base "environment" config for
208
+ // all environments, we will start with those defaults.
209
+ ...DEFAULT_ENVIRONMENT_CONFIG,
210
+
211
+ // When the current directory has no configuration file we support a zero-config mode of operation.
212
+ // This works by using the default options and inferring how to map the current directory based
213
+ // on the contents of the directory.
214
+ core:
215
+ detectedDirectoryType === 'core'
216
+ ? '.'
217
+ : DEFAULT_ENVIRONMENT_CONFIG.core,
218
+ plugins:
219
+ detectedDirectoryType === 'plugin'
220
+ ? [ '.' ]
221
+ : DEFAULT_ENVIRONMENT_CONFIG.plugins,
222
+ themes:
223
+ detectedDirectoryType === 'theme'
224
+ ? [ '.' ]
225
+ : DEFAULT_ENVIRONMENT_CONFIG.themes,
226
+
227
+ // These configuration options are root-only and should not be present
228
+ // on environment-specific configuration objects.
229
+ lifecycleScripts: {
230
+ afterStart: null,
231
+ afterClean: null,
232
+ afterDestroy: null,
195
233
  },
196
- afterSetup: null,
197
234
  env: {
198
235
  development: {},
199
236
  tests: {
200
- config: { WP_DEBUG: false, SCRIPT_DEBUG: false },
237
+ config: {
238
+ WP_DEBUG: false,
239
+ SCRIPT_DEBUG: false,
240
+ },
201
241
  },
202
242
  },
203
243
  };
@@ -220,6 +260,7 @@ function getEnvironmentVarOverrides( cacheDirectoryPath ) {
220
260
  // Create a service config object so we can merge it with the others
221
261
  // and override anything that the configuration options need to.
222
262
  const overrideConfig = {
263
+ lifecycleScripts: overrides.lifecycleScripts,
223
264
  env: {
224
265
  development: {},
225
266
  tests: {},
@@ -252,10 +293,6 @@ function getEnvironmentVarOverrides( cacheDirectoryPath ) {
252
293
  overrideConfig.env.tests.phpVersion = overrides.phpVersion;
253
294
  }
254
295
 
255
- if ( overrides.afterSetup ) {
256
- overrideConfig.afterSetup = overrides.afterSetup;
257
- }
258
-
259
296
  return overrideConfig;
260
297
  }
261
298
 
@@ -292,7 +329,10 @@ async function parseRootConfig( configFile, rawConfig, options ) {
292
329
  configFile,
293
330
  null,
294
331
  rawConfig,
295
- options
332
+ {
333
+ ...options,
334
+ rootConfig: true,
335
+ }
296
336
  );
297
337
 
298
338
  // Parse any root-only options.
@@ -300,18 +340,28 @@ async function parseRootConfig( configFile, rawConfig, options ) {
300
340
  checkPort( configFile, `testsPort`, rawConfig.testsPort );
301
341
  parsedConfig.testsPort = rawConfig.testsPort;
302
342
  }
303
- if ( rawConfig.afterSetup !== undefined ) {
304
- // Support null as a valid input.
305
- if ( rawConfig.afterSetup !== null ) {
306
- checkString( configFile, 'afterSetup', rawConfig.afterSetup );
307
- }
308
- parsedConfig.afterSetup = rawConfig.afterSetup;
343
+ parsedConfig.lifecycleScripts = {};
344
+ if ( rawConfig.lifecycleScripts ) {
345
+ checkObjectWithValues(
346
+ configFile,
347
+ 'lifecycleScripts',
348
+ rawConfig.lifecycleScripts,
349
+ [ 'null', 'string' ],
350
+ true
351
+ );
352
+ parsedConfig.lifecycleScripts = rawConfig.lifecycleScripts;
309
353
  }
310
354
 
311
355
  // Parse the environment-specific configs so they're accessible to the root.
312
356
  parsedConfig.env = {};
313
357
  if ( rawConfig.env ) {
314
- checkObjectWithValues( configFile, 'env', rawConfig.env, [ 'object' ] );
358
+ checkObjectWithValues(
359
+ configFile,
360
+ 'env',
361
+ rawConfig.env,
362
+ [ 'object' ],
363
+ false
364
+ );
315
365
  for ( const env in rawConfig.env ) {
316
366
  parsedConfig.env[ env ] = await parseEnvironmentConfig(
317
367
  configFile,
@@ -333,6 +383,7 @@ async function parseRootConfig( configFile, rawConfig, options ) {
333
383
  * @param {Object} config A config object to parse.
334
384
  * @param {Object} options
335
385
  * @param {string} options.cacheDirectoryPath Path to the work directory located in ~/.wp-env.
386
+ * @param {boolean} options.rootConfig Indicates whether or not this is the root config object.
336
387
  *
337
388
  * @return {Promise<WPEnvironmentConfig>} The environment config object.
338
389
  */
@@ -348,6 +399,36 @@ async function parseEnvironmentConfig(
348
399
 
349
400
  const environmentPrefix = environment ? environment + '.' : '';
350
401
 
402
+ // Before we move forward with parsing we should make sure that there aren't any
403
+ // configuration options that do not exist. This helps prevent silent failures
404
+ // when a user sets up their configuration incorrectly.
405
+ for ( const key in config ) {
406
+ if ( DEFAULT_ENVIRONMENT_CONFIG[ key ] !== undefined ) {
407
+ continue;
408
+ }
409
+
410
+ // We should also check root-only options for the root config
411
+ // because these aren't part of the above defaults but are
412
+ // configuration options that we will parse.
413
+ switch ( key ) {
414
+ case 'testsPort':
415
+ case 'lifecycleScripts':
416
+ case 'env': {
417
+ if ( options.rootConfig ) {
418
+ continue;
419
+ }
420
+
421
+ break;
422
+ }
423
+ }
424
+
425
+ throw new ValidationError(
426
+ `Invalid ${ configFile }: "${ environmentPrefix }${ key }" is not a configuration option.`
427
+ );
428
+ }
429
+
430
+ // Parse each option individually so that we can handle the validation
431
+ // and any conversion that is required to use the option.
351
432
  const parsedConfig = {};
352
433
 
353
434
  if ( config.port !== undefined ) {
@@ -401,7 +482,8 @@ async function parseEnvironmentConfig(
401
482
  configFile,
402
483
  `${ environmentPrefix }config`,
403
484
  config.config,
404
- [ 'string', 'number', 'boolean', 'empty' ]
485
+ [ 'string', 'number', 'boolean' ],
486
+ true
405
487
  );
406
488
  parsedConfig.config = config.config;
407
489
 
@@ -426,7 +508,8 @@ async function parseEnvironmentConfig(
426
508
  configFile,
427
509
  `${ environmentPrefix }mappings`,
428
510
  config.mappings,
429
- [ 'string' ]
511
+ [ 'string' ],
512
+ false
430
513
  );
431
514
  parsedConfig.mappings = Object.entries( config.mappings ).reduce(
432
515
  ( result, [ wpDir, localDir ] ) => {
@@ -59,9 +59,9 @@ function mergeRootToEnvironments( config ) {
59
59
  config.env.tests.port = config.testsPort;
60
60
  delete config.testsPort;
61
61
  }
62
- if ( config.afterSetup !== undefined ) {
63
- removedRootOptions.afterSetup = config.afterSetup;
64
- delete config.afterSetup;
62
+ if ( config.lifecycleScripts !== undefined ) {
63
+ removedRootOptions.lifecycleScripts = config.lifecycleScripts;
64
+ delete config.lifecycleScripts;
65
65
  }
66
66
 
67
67
  // Merge the root config and the environment configs together so that
@@ -2,13 +2,13 @@
2
2
 
3
3
  exports[`Config Integration should load local and override configuration files 1`] = `
4
4
  {
5
- "afterSetup": null,
6
5
  "configDirectoryPath": "/test/gutenberg",
7
6
  "detectedLocalConfig": true,
8
7
  "dockerComposeConfigPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338/docker-compose.yml",
9
8
  "env": {
10
9
  "development": {
11
10
  "config": {
11
+ "FS_METHOD": "direct",
12
12
  "SCRIPT_DEBUG": true,
13
13
  "WP_DEBUG": true,
14
14
  "WP_ENVIRONMENT_TYPE": "local",
@@ -36,6 +36,7 @@ exports[`Config Integration should load local and override configuration files 1
36
36
  },
37
37
  "tests": {
38
38
  "config": {
39
+ "FS_METHOD": "direct",
39
40
  "SCRIPT_DEBUG": false,
40
41
  "WP_DEBUG": false,
41
42
  "WP_ENVIRONMENT_TYPE": "local",
@@ -62,6 +63,11 @@ exports[`Config Integration should load local and override configuration files 1
62
63
  "themeSources": [],
63
64
  },
64
65
  },
66
+ "lifecycleScripts": {
67
+ "afterClean": null,
68
+ "afterDestroy": "test",
69
+ "afterStart": null,
70
+ },
65
71
  "name": "gutenberg",
66
72
  "workDirectoryPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338",
67
73
  }
@@ -69,13 +75,13 @@ exports[`Config Integration should load local and override configuration files 1
69
75
 
70
76
  exports[`Config Integration should load local configuration file 1`] = `
71
77
  {
72
- "afterSetup": "test",
73
78
  "configDirectoryPath": "/test/gutenberg",
74
79
  "detectedLocalConfig": true,
75
80
  "dockerComposeConfigPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338/docker-compose.yml",
76
81
  "env": {
77
82
  "development": {
78
83
  "config": {
84
+ "FS_METHOD": "direct",
79
85
  "SCRIPT_DEBUG": true,
80
86
  "WP_DEBUG": true,
81
87
  "WP_ENVIRONMENT_TYPE": "local",
@@ -103,6 +109,7 @@ exports[`Config Integration should load local configuration file 1`] = `
103
109
  },
104
110
  "tests": {
105
111
  "config": {
112
+ "FS_METHOD": "direct",
106
113
  "SCRIPT_DEBUG": false,
107
114
  "WP_DEBUG": false,
108
115
  "WP_ENVIRONMENT_TYPE": "local",
@@ -129,6 +136,11 @@ exports[`Config Integration should load local configuration file 1`] = `
129
136
  "themeSources": [],
130
137
  },
131
138
  },
139
+ "lifecycleScripts": {
140
+ "afterClean": null,
141
+ "afterDestroy": null,
142
+ "afterStart": "test",
143
+ },
132
144
  "name": "gutenberg",
133
145
  "workDirectoryPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338",
134
146
  }
@@ -136,13 +148,13 @@ exports[`Config Integration should load local configuration file 1`] = `
136
148
 
137
149
  exports[`Config Integration should use default configuration 1`] = `
138
150
  {
139
- "afterSetup": null,
140
151
  "configDirectoryPath": "/test/gutenberg",
141
152
  "detectedLocalConfig": true,
142
153
  "dockerComposeConfigPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338/docker-compose.yml",
143
154
  "env": {
144
155
  "development": {
145
156
  "config": {
157
+ "FS_METHOD": "direct",
146
158
  "SCRIPT_DEBUG": true,
147
159
  "WP_DEBUG": true,
148
160
  "WP_ENVIRONMENT_TYPE": "local",
@@ -170,6 +182,7 @@ exports[`Config Integration should use default configuration 1`] = `
170
182
  },
171
183
  "tests": {
172
184
  "config": {
185
+ "FS_METHOD": "direct",
173
186
  "SCRIPT_DEBUG": false,
174
187
  "WP_DEBUG": false,
175
188
  "WP_ENVIRONMENT_TYPE": "local",
@@ -196,6 +209,11 @@ exports[`Config Integration should use default configuration 1`] = `
196
209
  "themeSources": [],
197
210
  },
198
211
  },
212
+ "lifecycleScripts": {
213
+ "afterClean": null,
214
+ "afterDestroy": null,
215
+ "afterStart": null,
216
+ },
199
217
  "name": "gutenberg",
200
218
  "workDirectoryPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338",
201
219
  }
@@ -203,13 +221,13 @@ exports[`Config Integration should use default configuration 1`] = `
203
221
 
204
222
  exports[`Config Integration should use environment variables over local and override configuration files 1`] = `
205
223
  {
206
- "afterSetup": "test",
207
224
  "configDirectoryPath": "/test/gutenberg",
208
225
  "detectedLocalConfig": true,
209
226
  "dockerComposeConfigPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338/docker-compose.yml",
210
227
  "env": {
211
228
  "development": {
212
229
  "config": {
230
+ "FS_METHOD": "direct",
213
231
  "SCRIPT_DEBUG": true,
214
232
  "WP_DEBUG": true,
215
233
  "WP_ENVIRONMENT_TYPE": "local",
@@ -238,6 +256,7 @@ exports[`Config Integration should use environment variables over local and over
238
256
  },
239
257
  "tests": {
240
258
  "config": {
259
+ "FS_METHOD": "direct",
241
260
  "SCRIPT_DEBUG": false,
242
261
  "WP_DEBUG": false,
243
262
  "WP_ENVIRONMENT_TYPE": "local",
@@ -265,6 +284,11 @@ exports[`Config Integration should use environment variables over local and over
265
284
  "themeSources": [],
266
285
  },
267
286
  },
287
+ "lifecycleScripts": {
288
+ "afterClean": null,
289
+ "afterDestroy": null,
290
+ "afterStart": "test",
291
+ },
268
292
  "name": "gutenberg",
269
293
  "workDirectoryPath": "/cache/5fea4c5689ef6cc4a4e6eaaa39323338",
270
294
  }
@@ -3,7 +3,6 @@
3
3
  /**
4
4
  * External dependencies
5
5
  */
6
- const path = require( 'path' );
7
6
  const { readFile } = require( 'fs' ).promises;
8
7
 
9
8
  /**
@@ -48,7 +47,7 @@ describe( 'Config Integration', () => {
48
47
  delete process.env.WP_ENV_HOME;
49
48
  delete process.env.WP_ENV_PORT;
50
49
  delete process.env.WP_ENV_TESTS_PORT;
51
- delete process.env.WP_ENV_AFTER_SETUP;
50
+ delete process.env.WP_ENV_LIFECYCLE_SCRIPT_AFTER_START;
52
51
  } );
53
52
 
54
53
  it( 'should use default configuration', async () => {
@@ -69,14 +68,18 @@ describe( 'Config Integration', () => {
69
68
  return JSON.stringify( {
70
69
  core: 'WordPress/WordPress#trunk',
71
70
  port: 123,
72
- afterSetup: 'test',
71
+ lifecycleScripts: {
72
+ afterStart: 'test',
73
+ afterClean: null,
74
+ afterDestroy: null,
75
+ },
73
76
  } );
74
77
  }
75
78
 
76
79
  throw { code: 'ENOENT' };
77
80
  } );
78
81
 
79
- const config = await loadConfig( path.resolve( '/test/gutenberg' ) );
82
+ const config = await loadConfig( '/test/gutenberg' );
80
83
 
81
84
  expect( config.env.development.port ).toEqual( 123 );
82
85
  expect( config.env.tests.port ).toEqual( 8889 );
@@ -90,19 +93,29 @@ describe( 'Config Integration', () => {
90
93
  core: 'WordPress/WordPress#trunk',
91
94
  port: 123,
92
95
  testsPort: 456,
96
+ lifecycleScripts: {
97
+ afterStart: 'test',
98
+ afterClean: null,
99
+ afterDestroy: null,
100
+ },
93
101
  } );
94
102
  }
95
103
 
96
104
  if ( fileName === '/test/gutenberg/.wp-env.override.json' ) {
97
105
  return JSON.stringify( {
98
106
  port: 999,
107
+ lifecycleScripts: {
108
+ afterStart: null,
109
+ afterClean: null,
110
+ afterDestroy: 'test',
111
+ },
99
112
  } );
100
113
  }
101
114
 
102
115
  throw { code: 'ENOENT' };
103
116
  } );
104
117
 
105
- const config = await loadConfig( path.resolve( '/test/gutenberg' ) );
118
+ const config = await loadConfig( '/test/gutenberg' );
106
119
 
107
120
  expect( config.env.development.port ).toEqual( 999 );
108
121
  expect( config.env.tests.port ).toEqual( 456 );
@@ -112,7 +125,7 @@ describe( 'Config Integration', () => {
112
125
  it( 'should use environment variables over local and override configuration files', async () => {
113
126
  process.env.WP_ENV_PORT = 12345;
114
127
  process.env.WP_ENV_TESTS_PORT = 61234;
115
- process.env.WP_ENV_AFTER_SETUP = 'test';
128
+ process.env.WP_ENV_LIFECYCLE_SCRIPT_AFTER_START = 'test';
116
129
 
117
130
  readFile.mockImplementation( async ( fileName ) => {
118
131
  if ( fileName === '/test/gutenberg/.wp-env.json' ) {
@@ -120,7 +133,11 @@ describe( 'Config Integration', () => {
120
133
  core: 'WordPress/WordPress#trunk',
121
134
  port: 123,
122
135
  testsPort: 456,
123
- afterSetup: 'local',
136
+ lifecycleScripts: {
137
+ afterStart: 'local',
138
+ afterClean: null,
139
+ afterDestroy: null,
140
+ },
124
141
  } );
125
142
  }
126
143
 
@@ -133,10 +150,14 @@ describe( 'Config Integration', () => {
133
150
  throw { code: 'ENOENT' };
134
151
  } );
135
152
 
136
- const config = await loadConfig( path.resolve( '/test/gutenberg' ) );
153
+ const config = await loadConfig( '/test/gutenberg' );
137
154
 
138
155
  expect( config.env.development.port ).toEqual( 12345 );
139
156
  expect( config.env.tests.port ).toEqual( 61234 );
157
+ expect( config.lifecycleScripts ).toHaveProperty(
158
+ 'afterStart',
159
+ 'test'
160
+ );
140
161
  expect( config ).toMatchSnapshot();
141
162
  } );
142
163
  } );
@@ -16,12 +16,18 @@ describe( 'mergeConfigs', () => {
16
16
  config: {
17
17
  WP_TEST: 'test',
18
18
  },
19
+ lifecycleScripts: {
20
+ afterStart: 'test',
21
+ },
19
22
  },
20
23
  {
21
24
  port: 8889,
22
25
  config: {
23
26
  WP_TEST_2: 'test-2',
24
27
  },
28
+ lifecycleScripts: {
29
+ afterDestroy: 'test-2',
30
+ },
25
31
  }
26
32
  );
27
33
 
@@ -35,6 +41,10 @@ describe( 'mergeConfigs', () => {
35
41
  WP_TEST: 'test',
36
42
  WP_TEST_2: 'test-2',
37
43
  },
44
+ lifecycleScripts: {
45
+ afterStart: 'test',
46
+ afterDestroy: 'test-2',
47
+ },
38
48
  } );
39
49
  } );
40
50