@wordpress/env 6.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.
Files changed (55) hide show
  1. package/README.md +130 -66
  2. package/lib/build-docker-compose-config.js +67 -96
  3. package/lib/cache.js +1 -0
  4. package/lib/cli.js +39 -10
  5. package/lib/commands/clean.js +13 -1
  6. package/lib/commands/destroy.js +21 -42
  7. package/lib/commands/index.js +1 -0
  8. package/lib/commands/install-path.js +1 -0
  9. package/lib/commands/logs.js +1 -0
  10. package/lib/commands/run.js +45 -21
  11. package/lib/commands/start.js +29 -8
  12. package/lib/commands/stop.js +1 -0
  13. package/lib/config/add-or-replace-port.js +12 -3
  14. package/lib/config/db-env.js +1 -0
  15. package/lib/config/detect-directory-type.js +1 -1
  16. package/lib/config/get-cache-directory.js +39 -0
  17. package/lib/config/get-config-from-environment-vars.js +106 -0
  18. package/lib/config/index.js +7 -5
  19. package/lib/config/load-config.js +92 -0
  20. package/lib/config/merge-configs.js +105 -0
  21. package/lib/config/parse-config.js +501 -157
  22. package/lib/config/parse-source-string.js +164 -0
  23. package/lib/config/post-process-config.js +202 -0
  24. package/lib/config/read-raw-config-file.js +7 -33
  25. package/lib/config/test/__snapshots__/config-integration.js.snap +295 -0
  26. package/lib/config/test/add-or-replace-port.js +29 -1
  27. package/lib/config/test/config-integration.js +164 -0
  28. package/lib/config/test/get-cache-directory.js +57 -0
  29. package/lib/config/test/merge-configs.js +121 -0
  30. package/lib/config/test/parse-config.js +393 -0
  31. package/lib/config/test/parse-source-string.js +154 -0
  32. package/lib/config/test/post-process-config.js +299 -0
  33. package/lib/config/test/read-raw-config-file.js +19 -63
  34. package/lib/config/test/validate-config.js +363 -0
  35. package/lib/config/validate-config.js +119 -54
  36. package/lib/env.js +2 -0
  37. package/lib/execute-lifecycle-script.js +86 -0
  38. package/lib/get-host-user.js +27 -0
  39. package/lib/init-config.js +186 -63
  40. package/lib/md5.js +1 -0
  41. package/lib/parse-xdebug-mode.js +1 -0
  42. package/lib/retry.js +1 -0
  43. package/lib/test/__snapshots__/md5.js.snap +9 -0
  44. package/lib/test/build-docker-compose-config.js +134 -0
  45. package/lib/test/cache.js +154 -0
  46. package/lib/test/cli.js +149 -0
  47. package/lib/test/execute-lifecycle-script.js +59 -0
  48. package/lib/test/md5.js +35 -0
  49. package/lib/test/parse-xdebug-mode.js +41 -0
  50. package/lib/validate-run-container.js +41 -0
  51. package/lib/wordpress.js +4 -19
  52. package/package.json +2 -2
  53. package/lib/config/config.js +0 -353
  54. package/lib/config/test/__snapshots__/config.js.snap +0 -83
  55. package/lib/config/test/config.js +0 -1241
@@ -0,0 +1,164 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const path = require( 'path' );
6
+ const os = require( 'os' );
7
+
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ const { ValidationError } = require( './validate-config' );
12
+
13
+ /**
14
+ * A WordPress installation, plugin or theme to be loaded into the environment.
15
+ *
16
+ * @typedef WPSource
17
+ * @property {'local'|'git'|'zip'} type The source type.
18
+ * @property {string} path The path to the WordPress installation, plugin or theme.
19
+ * @property {?string} url The URL to the source download if the source type is not local.
20
+ * @property {?string} ref The git ref for the source if the source type is 'git'.
21
+ * @property {string} basename Name that identifies the WordPress installation, plugin or theme.
22
+ */
23
+
24
+ /**
25
+ * The string at the beginning of a source path that points to a home-relative
26
+ * directory. Will be '~/' on unix environments and '~\' on Windows.
27
+ */
28
+ const HOME_PATH_PREFIX = `~${ path.sep }`;
29
+
30
+ /**
31
+ * Parses a source string into a source object.
32
+ *
33
+ * @param {?string} sourceString The source string. See README.md for documentation on valid source string patterns.
34
+ * @param {Object} options
35
+ * @param {string} options.cacheDirectoryPath Path to the work directory located in ~/.wp-env.
36
+ *
37
+ * @return {?WPSource} A source object.
38
+ */
39
+ function parseSourceString( sourceString, { cacheDirectoryPath } ) {
40
+ if ( sourceString === null ) {
41
+ return null;
42
+ }
43
+
44
+ if (
45
+ sourceString.startsWith( '.' ) ||
46
+ sourceString.startsWith( HOME_PATH_PREFIX ) ||
47
+ path.isAbsolute( sourceString )
48
+ ) {
49
+ let sourcePath;
50
+ if ( sourceString.startsWith( HOME_PATH_PREFIX ) ) {
51
+ sourcePath = path.resolve(
52
+ os.homedir(),
53
+ sourceString.slice( HOME_PATH_PREFIX.length )
54
+ );
55
+ } else {
56
+ sourcePath = path.resolve( sourceString );
57
+ }
58
+ const basename = path.basename( sourcePath );
59
+ return {
60
+ type: 'local',
61
+ path: sourcePath,
62
+ basename,
63
+ };
64
+ }
65
+
66
+ const zipFields = sourceString.match(
67
+ /^https?:\/\/([^\s$.?#].[^\s]*)\.zip(\?.+)?$/
68
+ );
69
+
70
+ if ( zipFields ) {
71
+ const wpOrgFields = sourceString.match(
72
+ /^https?:\/\/downloads\.wordpress\.org\/(?:plugin|theme)\/([^\s\.]*)([^\s]*)?\.zip$/
73
+ );
74
+ const basename = wpOrgFields
75
+ ? encodeURIComponent( wpOrgFields[ 1 ] )
76
+ : encodeURIComponent( path.basename( zipFields[ 1 ] ) );
77
+
78
+ return {
79
+ type: 'zip',
80
+ url: sourceString,
81
+ path: path.resolve( cacheDirectoryPath, basename ),
82
+ basename,
83
+ };
84
+ }
85
+
86
+ // SSH URLs (git)
87
+ const supportedProtocols = [ 'ssh:', 'git+ssh:' ];
88
+ try {
89
+ const sshUrl = new URL( sourceString );
90
+ if ( supportedProtocols.includes( sshUrl.protocol ) ) {
91
+ const pathElements = sshUrl.pathname
92
+ .split( '/' )
93
+ .filter( ( e ) => !! e );
94
+ const basename = pathElements
95
+ .slice( -1 )[ 0 ]
96
+ .replace( /\.git/, '' );
97
+ const workingPath = path.resolve(
98
+ cacheDirectoryPath,
99
+ ...pathElements.slice( 0, -1 ),
100
+ basename
101
+ );
102
+ return {
103
+ type: 'git',
104
+ url: sshUrl.href.split( '#' )[ 0 ],
105
+ ref: sshUrl.hash.slice( 1 ) || undefined,
106
+ path: workingPath,
107
+ clonePath: workingPath,
108
+ basename,
109
+ };
110
+ }
111
+ } catch ( err ) {}
112
+
113
+ const gitHubFields = sourceString.match(
114
+ /^([^\/]+)\/([^#\/]+)(\/([^#]+))?(?:#(.+))?$/
115
+ );
116
+
117
+ if ( gitHubFields ) {
118
+ return {
119
+ type: 'git',
120
+ url: `https://github.com/${ gitHubFields[ 1 ] }/${ gitHubFields[ 2 ] }.git`,
121
+ ref: gitHubFields[ 5 ],
122
+ path: path.resolve(
123
+ cacheDirectoryPath,
124
+ gitHubFields[ 2 ],
125
+ gitHubFields[ 4 ] || '.'
126
+ ),
127
+ clonePath: path.resolve( cacheDirectoryPath, gitHubFields[ 2 ] ),
128
+ basename: gitHubFields[ 4 ] || gitHubFields[ 2 ],
129
+ };
130
+ }
131
+
132
+ throw new ValidationError(
133
+ `Invalid or unrecognized source: "${ sourceString }".`
134
+ );
135
+ }
136
+
137
+ /**
138
+ * Given a source object, returns a new source object with the testsPath
139
+ * property set correctly. Only the 'core' source requires a testsPath.
140
+ *
141
+ * @param {?WPSource} source A source object.
142
+ * @param {Object} options
143
+ * @param {string} options.cacheDirectoryPath Path to the work directory located in ~/.wp-env.
144
+ *
145
+ * @return {?WPSource} A source object.
146
+ */
147
+ function includeTestsPath( source, { cacheDirectoryPath } ) {
148
+ if ( source === null ) {
149
+ return null;
150
+ }
151
+
152
+ return {
153
+ ...source,
154
+ testsPath: path.resolve(
155
+ cacheDirectoryPath,
156
+ 'tests-' + path.basename( source.path )
157
+ ),
158
+ };
159
+ }
160
+
161
+ module.exports = {
162
+ parseSourceString,
163
+ includeTestsPath,
164
+ };
@@ -0,0 +1,202 @@
1
+ 'use strict';
2
+ /**
3
+ * Internal dependencies
4
+ */
5
+ const mergeConfigs = require( './merge-configs' );
6
+ const addOrReplacePort = require( './add-or-replace-port' );
7
+ const { ValidationError } = require( './validate-config' );
8
+
9
+ /**
10
+ * @typedef {import('./parse-config').WPRootConfig} WPRootConfig
11
+ * @typedef {import('./parse-config').WPEnvironmentConfig} WPEnvironmentConfig
12
+ */
13
+
14
+ /**
15
+ * Performs any additional post-processing on the config object.
16
+ *
17
+ * @param {WPEnvironmentConfig} config The config to process.
18
+ *
19
+ * @return {WPEnvironmentConfig} The config after post-processing.
20
+ */
21
+ module.exports = function postProcessConfig( config ) {
22
+ // Make sure that we're operating on a config object that has
23
+ // complete environment configs for convenience.
24
+ config = mergeRootToEnvironments( config );
25
+
26
+ config = appendPortToWPConfigs( config );
27
+
28
+ validate( config );
29
+ return config;
30
+ };
31
+
32
+ /**
33
+ * Merges the root config and each environment together in order to make sure each environment has
34
+ * a full config object to work with internally. This makes it easier than having to try and
35
+ * resolve the full config options every time we want to use them for something.
36
+ *
37
+ * @param {WPEnvironmentConfig} config The config to process.
38
+ *
39
+ * @return {WPRootConfig} The config object with the root options merged together with the environment-specific options.
40
+ */
41
+ function mergeRootToEnvironments( config ) {
42
+ // Some root-level options need to be handled early because they have a special
43
+ // cascade behavior that would break the normal merge. After merging we then
44
+ // delete them to avoid that breakage and add them back before we return.
45
+ const removedRootOptions = {};
46
+ if (
47
+ config.port !== undefined &&
48
+ config.env.development.port === undefined
49
+ ) {
50
+ removedRootOptions.port = config.port;
51
+ config.env.development.port = config.port;
52
+ delete config.port;
53
+ }
54
+ if (
55
+ config.testsPort !== undefined &&
56
+ config.env.tests.port === undefined
57
+ ) {
58
+ removedRootOptions.testsPort = config.testsPort;
59
+ config.env.tests.port = config.testsPort;
60
+ delete config.testsPort;
61
+ }
62
+ if ( config.lifecycleScripts !== undefined ) {
63
+ removedRootOptions.lifecycleScripts = config.lifecycleScripts;
64
+ delete config.lifecycleScripts;
65
+ }
66
+
67
+ // Merge the root config and the environment configs together so that
68
+ // we can ignore the root config and have full environment configs.
69
+ for ( const env in config.env ) {
70
+ config.env[ env ] = mergeConfigs(
71
+ deepCopyRootOptions( config ),
72
+ config.env[ env ]
73
+ );
74
+ }
75
+
76
+ // Set any root-level options we reset back.
77
+ for ( const option in removedRootOptions ) {
78
+ config[ option ] = removedRootOptions[ option ];
79
+ }
80
+
81
+ return config;
82
+ }
83
+
84
+ /**
85
+ * Appends the configured port to certain wp-config options.
86
+ *
87
+ * @param {WPRootConfig} config The config to process.
88
+ *
89
+ * @return {WPRootConfig} The config after post-processing.
90
+ */
91
+ function appendPortToWPConfigs( config ) {
92
+ const options = [ 'WP_TESTS_DOMAIN', 'WP_SITEURL', 'WP_HOME' ];
93
+
94
+ // We are only interested in editing the config options for environment-specific configs.
95
+ // If we made this change to the root config it would cause problems since they would
96
+ // be mapped to all environments even though the ports will be different.
97
+ for ( const env in config.env ) {
98
+ // There's nothing to do without any wp-config options set.
99
+ if ( config.env[ env ].config === undefined ) {
100
+ continue;
101
+ }
102
+
103
+ if ( config.env[ env ].port === undefined ) {
104
+ continue;
105
+ }
106
+
107
+ // Make sure that the port is on the option if it's present.
108
+ for ( const option of options ) {
109
+ if ( config.env[ env ].config[ option ] === undefined ) {
110
+ continue;
111
+ }
112
+
113
+ config.env[ env ].config[ option ] = addOrReplacePort(
114
+ config.env[ env ].config[ option ],
115
+ config.env[ env ].port,
116
+ // Don't replace the port if one is already set on WP_HOME.
117
+ option !== 'WP_HOME'
118
+ );
119
+ }
120
+ }
121
+
122
+ return config;
123
+ }
124
+
125
+ /**
126
+ * Examines the config to make sure that none of the environments share the same port.
127
+ *
128
+ * @param {WPRootConfig} config The config to process.
129
+ */
130
+ function validatePortUniqueness( config ) {
131
+ // We're going to build a map of the environments and their port
132
+ // so we can accomodate root-level config options more easily.
133
+ const environmentPorts = {};
134
+
135
+ // Add all of the environments to the map. This will
136
+ // overwrite any root-level options if necessary.
137
+ for ( const env in config.env ) {
138
+ if ( config.env[ env ].port === undefined ) {
139
+ throw new ValidationError(
140
+ `The "${ env }" environment has an invalid port.`
141
+ );
142
+ }
143
+
144
+ environmentPorts[ env ] = config.env[ env ].port;
145
+ }
146
+
147
+ // This search isn't very performant, but, we won't ever be
148
+ // checking more than a few entries so it doesn't matter.
149
+ for ( const env in environmentPorts ) {
150
+ for ( const check in environmentPorts ) {
151
+ if ( env === check ) {
152
+ continue;
153
+ }
154
+
155
+ if ( environmentPorts[ env ] === environmentPorts[ check ] ) {
156
+ throw new ValidationError(
157
+ `The "${ env }" and "${ check }" environments may not have the same port.`
158
+ );
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Perform any validation that can only happen after post-processing has occurred.
166
+ *
167
+ * @param {WPRootConfig} config The config to validate.
168
+ */
169
+ function validate( config ) {
170
+ validatePortUniqueness( config );
171
+ }
172
+
173
+ /**
174
+ * Creates a deep copy of the root options in the config object that we can use to avoid
175
+ * accidentally sharing object state between different environments.
176
+ *
177
+ * @param {WPRootConfig} config The root config object to copy.
178
+ *
179
+ * @return {WPRootConfig} A deep copy of the root config object.
180
+ */
181
+ function deepCopyRootOptions( config ) {
182
+ // Create a shallow clone of the object first so we can operate on it safetly.
183
+ const rootConfig = Object.assign( {}, config );
184
+
185
+ // Since we're only dealing with the root options we don't want the environments.
186
+ delete rootConfig.env;
187
+
188
+ if ( rootConfig.config !== undefined ) {
189
+ rootConfig.config = Object.assign( {}, rootConfig.config );
190
+ }
191
+ if ( rootConfig.mappings !== undefined ) {
192
+ rootConfig.mappings = Object.assign( {}, rootConfig.mappings );
193
+ }
194
+ if ( rootConfig.pluginSources !== undefined ) {
195
+ rootConfig.pluginSources = [ ...rootConfig.pluginSources ];
196
+ }
197
+ if ( rootConfig.themeSources !== undefined ) {
198
+ rootConfig.themeSources = [ ...rootConfig.themeSources ];
199
+ }
200
+
201
+ return rootConfig;
202
+ }
@@ -4,6 +4,7 @@
4
4
  */
5
5
  const fs = require( 'fs' ).promises;
6
6
 
7
+ const path = require( 'path' );
7
8
  /**
8
9
  * Internal dependencies
9
10
  */
@@ -13,53 +14,26 @@ const { ValidationError } = require( './validate-config' );
13
14
  * Reads the config JSON from the filesystem and returns it as a JS object
14
15
  * compatible with the env package.
15
16
  *
16
- * @param {string} name The name of the file for error messages.
17
17
  * @param {string} configPath The path to the JSON file to read.
18
18
  *
19
19
  * @return {Object} the raw config data.
20
20
  */
21
- module.exports = async function readRawConfigFile( name, configPath ) {
21
+ module.exports = async function readRawConfigFile( configPath ) {
22
22
  try {
23
- return withBackCompat(
24
- JSON.parse( await fs.readFile( configPath, 'utf8' ) )
25
- );
23
+ return JSON.parse( await fs.readFile( configPath, 'utf8' ) );
26
24
  } catch ( error ) {
25
+ const fileName = path.basename( configPath );
26
+
27
27
  if ( error.code === 'ENOENT' ) {
28
28
  return null;
29
29
  } else if ( error instanceof SyntaxError ) {
30
30
  throw new ValidationError(
31
- `Invalid ${ name }: ${ error.message }`
31
+ `Invalid ${ fileName }: ${ error.message }`
32
32
  );
33
33
  } else {
34
34
  throw new ValidationError(
35
- `Could not read ${ name }: ${ error.message }`
35
+ `Could not read ${ fileName }: ${ error.message }`
36
36
  );
37
37
  }
38
38
  }
39
39
  };
40
-
41
- /**
42
- * Used to maintain back compatibility with older versions of the .wp-env.json
43
- * file. Returns an object in the shape of the currently expected .wp-env.json
44
- * version.
45
- *
46
- * @param {Object} rawConfig config right after being read from a file.
47
- * @return {Object} Same config with any old-format values converted into the
48
- * shape of the currently expected format.
49
- */
50
- function withBackCompat( rawConfig ) {
51
- // Convert testsPort into new env.tests format.
52
- if ( rawConfig.testsPort !== undefined ) {
53
- rawConfig.env = {
54
- ...( rawConfig.env || {} ),
55
- tests: {
56
- port: rawConfig.testsPort,
57
- ...( rawConfig.env && rawConfig.env.tests
58
- ? rawConfig.env.tests
59
- : {} ),
60
- },
61
- };
62
- }
63
- delete rawConfig.testsPort;
64
- return rawConfig;
65
- }