@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,27 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const os = require( 'os' );
6
+
7
+ /**
8
+ * Gets information about the host user.
9
+ *
10
+ * @return {Object} The host user's name, uid, and gid.
11
+ */
12
+ module.exports = function getHostUser() {
13
+ const hostUser = os.userInfo();
14
+
15
+ // On Windows the uid and gid will be -1. Since there isn't a great way to handle this,
16
+ // we're just going to assign them to 1000. Docker Desktop already takes care of
17
+ // permission-related issues using magic, so this should be fine.
18
+ const uid = ( hostUser.uid === -1 ? 1000 : hostUser.uid ).toString();
19
+ const gid = ( hostUser.gid === -1 ? 1000 : hostUser.gid ).toString();
20
+
21
+ return {
22
+ name: hostUser.username,
23
+ uid,
24
+ gid,
25
+ fullUser: uid + ':' + gid,
26
+ };
27
+ };
@@ -1,3 +1,4 @@
1
+ 'use strict';
1
2
  /**
2
3
  * External dependencies
3
4
  */
@@ -5,12 +6,11 @@ const path = require( 'path' );
5
6
  const { writeFile, mkdir } = require( 'fs' ).promises;
6
7
  const { existsSync } = require( 'fs' );
7
8
  const yaml = require( 'js-yaml' );
8
- const os = require( 'os' );
9
9
 
10
10
  /**
11
11
  * Internal dependencies
12
12
  */
13
- const { readConfig } = require( './config' );
13
+ const { loadConfig, ValidationError } = require( './config' );
14
14
  const buildDockerComposeConfig = require( './build-docker-compose-config' );
15
15
 
16
16
  /**
@@ -39,8 +39,7 @@ module.exports = async function initConfig( {
39
39
  xdebug = 'off',
40
40
  writeChanges = false,
41
41
  } ) {
42
- const configPath = path.resolve( '.wp-env.json' );
43
- const config = await readConfig( configPath );
42
+ const config = await loadConfig( path.resolve( '.' ) );
44
43
  config.debug = debug;
45
44
 
46
45
  // Adding this to the config allows the start command to understand that the
@@ -81,13 +80,23 @@ module.exports = async function initConfig( {
81
80
  yaml.dump( dockerComposeConfig )
82
81
  );
83
82
 
84
- await writeFile(
85
- path.resolve( config.workDirectoryPath, 'Dockerfile' ),
86
- dockerFileContents(
87
- dockerComposeConfig.services.wordpress.image,
88
- config
89
- )
90
- );
83
+ // Write four Dockerfiles for each service we provided.
84
+ // (WordPress and CLI services, then a development and test environment for each.)
85
+ for ( const imageType of [ 'WordPress', 'CLI' ] ) {
86
+ for ( const envType of [ 'development', 'tests' ] ) {
87
+ await writeFile(
88
+ path.resolve(
89
+ config.workDirectoryPath,
90
+ `${
91
+ envType === 'tests' ? 'Tests-' : ''
92
+ }${ imageType }.Dockerfile`
93
+ ),
94
+ imageType === 'WordPress'
95
+ ? wordpressDockerFileContents( envType, config )
96
+ : cliDockerFileContents( envType, config )
97
+ );
98
+ }
99
+ }
91
100
  } else if ( ! existsSync( config.workDirectoryPath ) ) {
92
101
  spinner.fail(
93
102
  'wp-env has not yet been initialized. Please run `wp-env start` to install the WordPress instance before using any other commands. This is only necessary to set up the environment for the first time; it is typically not necessary for the instance to be running after that in order to use other commands.'
@@ -99,80 +108,194 @@ module.exports = async function initConfig( {
99
108
  };
100
109
 
101
110
  /**
102
- * Checks the configured PHP version
103
- * against the minimum version supported by Xdebug
111
+ * Generates the Dockerfile used by wp-env's `wordpress` and `tests-wordpress` instances.
104
112
  *
105
- * @param {WPConfig} config
106
- * @return {boolean} Whether the PHP version is supported by Xdebug
113
+ * @param {string} env The environment we're installing -- development or tests.
114
+ * @param {WPConfig} config The configuration object.
115
+ * @return {string} The dockerfile contents.
107
116
  */
108
- function checkXdebugPhpCompatibility( config ) {
109
- // By default, an undefined phpVersion uses the version on the docker image,
110
- // which is supported by Xdebug 3.
111
- const phpCompatibility = true;
112
-
113
- // If PHP version is defined
114
- // ensure it meets the Xdebug minimum compatibility requirment.
115
- if ( config.env.development.phpVersion ) {
116
- const versionTokens = config.env.development.phpVersion.split( '.' );
117
- const majorVer = parseInt( versionTokens[ 0 ] );
118
- const minorVer = parseInt( versionTokens[ 1 ] );
117
+ function wordpressDockerFileContents( env, config ) {
118
+ const phpVersion = config.env[ env ].phpVersion
119
+ ? ':php' + config.env[ env ].phpVersion
120
+ : '';
119
121
 
120
- if ( isNaN( majorVer ) || isNaN( minorVer ) ) {
121
- throw new Error(
122
- 'Something went wrong when parsing the PHP version.'
123
- );
124
- }
122
+ return `FROM wordpress${ phpVersion }
125
123
 
126
- // Xdebug 3 supports 7.2 and higher
127
- // Ensure user has specified a compatible PHP version.
128
- if ( majorVer < 7 || ( majorVer === 7 && minorVer < 2 ) ) {
129
- throw new Error( 'Cannot use XDebug 3 on PHP < 7.2.' );
130
- }
131
- }
124
+ # Update apt sources for archived versions of Debian.
125
+
126
+ # stretch (https://lists.debian.org/debian-devel-announce/2023/03/msg00006.html)
127
+ RUN sed -i 's|deb.debian.org/debian stretch|archive.debian.org/debian stretch|g' /etc/apt/sources.list
128
+ RUN sed -i 's|security.debian.org/debian-security stretch|archive.debian.org/debian-security stretch|g' /etc/apt/sources.list
129
+ RUN sed -i '/stretch-updates/d' /etc/apt/sources.list
132
130
 
133
- return phpCompatibility;
131
+ # Create the host's user so that we can match ownership in the container.
132
+ ARG HOST_USERNAME
133
+ ARG HOST_UID
134
+ ARG HOST_GID
135
+ # When the IDs are already in use we can still safely move on.
136
+ RUN groupadd -g $HOST_GID $HOST_USERNAME || true
137
+ RUN useradd -m -u $HOST_UID -g $HOST_GID $HOST_USERNAME || true
138
+
139
+ # Install any dependencies we need in the container.
140
+ ${ installDependencies( 'wordpress', env, config ) }`;
134
141
  }
135
142
 
136
143
  /**
137
- * Generates the Dockerfile used by wp-env's development instance.
144
+ * Generates the Dockerfile used by wp-env's `cli` and `tests-cli` instances.
138
145
  *
139
- * @param {string} image The base docker image to use.
146
+ * @param {string} env The environment we're installing -- development or tests.
140
147
  * @param {WPConfig} config The configuration object.
141
- *
142
148
  * @return {string} The dockerfile contents.
143
149
  */
144
- function dockerFileContents( image, config ) {
145
- // Don't install XDebug unless it is explicitly required.
146
- let shouldInstallXdebug = false;
150
+ function cliDockerFileContents( env, config ) {
151
+ const phpVersion = config.env[ env ].phpVersion
152
+ ? '-php' + config.env[ env ].phpVersion
153
+ : '';
154
+
155
+ return `FROM wordpress:cli${ phpVersion }
156
+
157
+ # Switch to root so we can create users.
158
+ USER root
159
+
160
+ # Create the host's user so that we can match ownership in the container.
161
+ ARG HOST_USERNAME
162
+ ARG HOST_UID
163
+ ARG HOST_GID
164
+ # When the IDs are already in use we can still safely move on.
165
+ RUN addgroup -g $HOST_GID $HOST_USERNAME || true
166
+ RUN adduser -h /home/$HOST_USERNAME -G $( getent group $HOST_GID | cut -d: -f1 ) -u $HOST_UID $HOST_USERNAME || true
167
+
168
+ # Install any dependencies we need in the container.
169
+ ${ installDependencies( 'cli', env, config ) }
170
+
171
+ # Switch back to the original user now that we're done.
172
+ USER www-data
173
+
174
+ # Have the container sleep infinitely to keep it alive for us to run commands on it.
175
+ CMD [ "/bin/sh", "-c", "while true; do sleep 2073600; done" ]
176
+ `;
177
+ }
178
+
179
+ /**
180
+ * Generates content for the Dockerfile to install dependencies.
181
+ *
182
+ * @param {string} service The kind of service that we're installing dependencies on ('wordpress' or 'cli').
183
+ * @param {string} env The environment we're installing dependencies for ('development' or 'tests').
184
+ * @param {WPConfig} config The configuration object.
185
+ * @return {string} The Dockerfile content for installing dependencies.
186
+ */
187
+ function installDependencies( service, env, config ) {
188
+ let dockerFileContent = '';
189
+
190
+ // At times we may need to evaluate the environment. This is because the
191
+ // WordPress image uses Ubuntu while the CLI image uses Alpine.
192
+
193
+ // Start with some environment-specific dependency installations.
194
+ switch ( service ) {
195
+ case 'wordpress': {
196
+ dockerFileContent += `
197
+ # Make sure we're working with the latest packages.
198
+ RUN apt-get -qy update
147
199
 
148
- if ( config.xdebug !== 'off' ) {
149
- const usingCompatiblePhp = checkXdebugPhpCompatibility( config );
200
+ # Install some basic PHP dependencies.
201
+ RUN apt-get -qy install $PHPIZE_DEPS && touch /usr/local/etc/php/php.ini
202
+
203
+ # Set up sudo so they can have root access.
204
+ RUN apt-get -qy install sudo
205
+ RUN echo "#$HOST_UID ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers`;
206
+ break;
207
+ }
208
+ case 'cli': {
209
+ dockerFileContent += `
210
+ # Make sure we're working with the latest packages.
211
+ RUN apk update
212
+
213
+ # Install some basic PHP dependencies.
214
+ RUN apk --no-cache add $PHPIZE_DEPS && touch /usr/local/etc/php/php.ini
150
215
 
151
- if ( usingCompatiblePhp ) {
152
- shouldInstallXdebug = true;
216
+ # Set up sudo so they can have root access.
217
+ RUN apk --no-cache add sudo linux-headers
218
+ RUN echo "#$HOST_UID ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers`;
219
+ break;
220
+ }
221
+ default: {
222
+ throw new Error( `Invalid service "${ service }" given` );
153
223
  }
154
224
  }
155
225
 
156
- return `FROM ${ image }
226
+ dockerFileContent += getXdebugConfig(
227
+ config.xdebug,
228
+ config.env[ env ].phpVersion
229
+ );
157
230
 
158
- RUN apt-get -qy install $PHPIZE_DEPS && touch /usr/local/etc/php/php.ini
159
- ${ shouldInstallXdebug ? installXdebug( config.xdebug ) : '' }
160
- `;
231
+ // Add better PHP settings.
232
+ dockerFileContent += `
233
+ RUN echo 'upload_max_filesize = 1G' >> /usr/local/etc/php/php.ini
234
+ RUN echo 'post_max_size = 1G' >> /usr/local/etc/php/php.ini`;
235
+
236
+ // Make sure Composer is available for use in all services.
237
+ dockerFileContent += `
238
+ RUN curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php
239
+ RUN export COMPOSER_HASH=\`curl -sS https://composer.github.io/installer.sig\` && php -r "if (hash_file('SHA384', '/tmp/composer-setup.php') === '$COMPOSER_HASH') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('/tmp/composer-setup.php'); } echo PHP_EOL;"
240
+ RUN php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer
241
+ RUN rm /tmp/composer-setup.php`;
242
+
243
+ // Install any Composer packages we might need globally.
244
+ // Make sure to do this as the user and ensure the binaries are available in the $PATH.
245
+ dockerFileContent += `
246
+ USER $HOST_UID:$HOST_GID
247
+ ENV PATH="\${PATH}:~/.composer/vendor/bin"
248
+ RUN composer global require --dev yoast/phpunit-polyfills:"^1.0"
249
+ USER root`;
250
+
251
+ return dockerFileContent;
161
252
  }
162
253
 
163
- function installXdebug( enableXdebug ) {
164
- const isLinux = os.type() === 'Linux';
165
- // Discover client host does not appear to work on macOS with Docker.
166
- const clientDetectSettings = isLinux
167
- ? 'xdebug.discover_client_host=true'
168
- : 'xdebug.client_host="host.docker.internal"';
254
+ /**
255
+ * Gets the Xdebug config based on the options in the config object.
256
+ *
257
+ * @param {string} xdebugMode The Xdebug mode set in the config.
258
+ * @param {string} phpVersion The php version set in the environment.
259
+ * @return {string} The Xdebug config -- can be an empty string when it's not used.
260
+ */
261
+ function getXdebugConfig( xdebugMode = 'off', phpVersion ) {
262
+ if ( xdebugMode === 'off' ) {
263
+ return '';
264
+ }
265
+
266
+ let xdebugVersion = 'xdebug';
267
+
268
+ if ( phpVersion ) {
269
+ const versionTokens = phpVersion.split( '.' );
270
+ const majorVer = parseInt( versionTokens[ 0 ] );
271
+ const minorVer = parseInt( versionTokens[ 1 ] );
272
+
273
+ if ( isNaN( majorVer ) || isNaN( minorVer ) ) {
274
+ throw new ValidationError(
275
+ 'Something went wrong when parsing the PHP version.'
276
+ );
277
+ }
278
+
279
+ // Throw an error if someone tries to use Xdebug with an unsupported PHP version.
280
+ // Xdebug 3 only supports 7.2 and higher.
281
+ if ( majorVer < 7 || ( majorVer === 7 && minorVer < 2 ) ) {
282
+ throw new ValidationError(
283
+ `Cannot use XDebug 3 with PHP < 7.2. Your PHP version is ${ phpVersion }.`
284
+ );
285
+ }
286
+
287
+ // For now, we support PHP 7 by installing the final version of Xdebug to
288
+ // support PHP 7 when the environment uses that version. By default, use the
289
+ // latest version.
290
+ if ( majorVer === 7 ) {
291
+ xdebugVersion = 'xdebug-3.1.6';
292
+ }
293
+ }
169
294
 
170
295
  return `
171
- # Install Xdebug:
172
- RUN if [ -z "$(pecl list | grep xdebug)" ] ; then pecl install xdebug ; fi
296
+ RUN if [ -z "$(pecl list | grep ${ xdebugVersion })" ] ; then pecl install ${ xdebugVersion } ; fi
173
297
  RUN docker-php-ext-enable xdebug
174
298
  RUN echo 'xdebug.start_with_request=yes' >> /usr/local/etc/php/php.ini
175
- RUN echo 'xdebug.mode=${ enableXdebug }' >> /usr/local/etc/php/php.ini
176
- RUN echo '${ clientDetectSettings }' >> /usr/local/etc/php/php.ini
177
- `;
299
+ RUN echo 'xdebug.mode=${ xdebugMode }' >> /usr/local/etc/php/php.ini
300
+ RUN echo 'xdebug.client_host="host.docker.internal"' >> /usr/local/etc/php/php.ini`;
178
301
  }
package/lib/md5.js CHANGED
@@ -1,3 +1,4 @@
1
+ 'use strict';
1
2
  /**
2
3
  * External dependencies
3
4
  */
@@ -1,3 +1,4 @@
1
+ 'use strict';
1
2
  // See https://xdebug.org/docs/all_settings#mode
2
3
  const XDEBUG_MODES = [
3
4
  'develop',
package/lib/retry.js CHANGED
@@ -1,3 +1,4 @@
1
+ 'use strict';
1
2
  /**
2
3
  * External dependencies
3
4
  */
@@ -0,0 +1,9 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`md5 creates a hash of a number 1`] = `"55587a910882016321201e6ebbc9f595"`;
4
+
5
+ exports[`md5 creates a hash of a object 1`] = `"a0294b0e88367a0ff800d78413356538"`;
6
+
7
+ exports[`md5 creates a hash of a string 1`] = `"5d41402abc4b2a76b9719d911017c592"`;
8
+
9
+ exports[`md5 creates a hash of null 1`] = `"37a6259cc0c1dae299a7866489dff0bd"`;
@@ -0,0 +1,134 @@
1
+ 'use strict';
2
+ /**
3
+ * Internal dependencies
4
+ */
5
+ const buildDockerComposeConfig = require( '../build-docker-compose-config' );
6
+ const getHostUser = require( '../get-host-user' );
7
+
8
+ // The basic config keys which build docker compose config requires.
9
+ const CONFIG = {
10
+ mappings: {},
11
+ pluginSources: [],
12
+ themeSources: [],
13
+ port: 8888,
14
+ configDirectoryPath: '/path/to/config',
15
+ };
16
+
17
+ jest.mock( '../get-host-user', () => jest.fn() );
18
+ getHostUser.mockImplementation( () => {
19
+ return {
20
+ name: 'test',
21
+ uid: 1,
22
+ gid: 2,
23
+ fullUser: '1:2',
24
+ };
25
+ } );
26
+
27
+ describe( 'buildDockerComposeConfig', () => {
28
+ it( 'should map directories before individual sources', () => {
29
+ const envConfig = {
30
+ ...CONFIG,
31
+ mappings: {
32
+ 'wp-content/plugins': {
33
+ path: '/path/to/wp-plugins',
34
+ },
35
+ },
36
+ pluginSources: [
37
+ { path: '/path/to/local/plugin', basename: 'test-name' },
38
+ ],
39
+ };
40
+ const dockerConfig = buildDockerComposeConfig( {
41
+ workDirectoryPath: '/path',
42
+ env: { development: envConfig, tests: envConfig },
43
+ } );
44
+ const { volumes } = dockerConfig.services.wordpress;
45
+ expect( volumes ).toEqual( [
46
+ 'wordpress:/var/www/html', // WordPress root.
47
+ '/path/WordPress-PHPUnit/tests/phpunit:/wordpress-phpunit', // WordPress test library,
48
+ 'user-home:/home/test',
49
+ '/path/to/wp-plugins:/var/www/html/wp-content/plugins', // Mapped plugins root.
50
+ '/path/to/local/plugin:/var/www/html/wp-content/plugins/test-name', // Mapped plugin.
51
+ ] );
52
+ } );
53
+
54
+ it( 'should add all specified sources to tests, dev, and cli services', () => {
55
+ const envConfig = {
56
+ ...CONFIG,
57
+ mappings: {
58
+ 'wp-content/plugins': {
59
+ path: '/path/to/wp-plugins',
60
+ },
61
+ },
62
+ pluginSources: [
63
+ { path: '/path/to/local/plugin', basename: 'test-name' },
64
+ ],
65
+ themeSources: [
66
+ { path: '/path/to/local/theme', basename: 'test-theme' },
67
+ ],
68
+ };
69
+ const dockerConfig = buildDockerComposeConfig( {
70
+ workDirectoryPath: '/path',
71
+ env: { development: envConfig, tests: envConfig },
72
+ } );
73
+ const devVolumes = dockerConfig.services.wordpress.volumes;
74
+ const cliVolumes = dockerConfig.services.cli.volumes;
75
+ expect( devVolumes ).toEqual( cliVolumes );
76
+
77
+ const testsVolumes = dockerConfig.services[ 'tests-wordpress' ].volumes;
78
+ const testsCliVolumes = dockerConfig.services[ 'tests-cli' ].volumes;
79
+ expect( testsVolumes ).toEqual( testsCliVolumes );
80
+
81
+ let localSources = [
82
+ '/path/to/wp-plugins:/var/www/html/wp-content/plugins',
83
+ '/path/WordPress-PHPUnit/tests/phpunit:/wordpress-phpunit',
84
+ 'user-home:/home/test',
85
+ '/path/to/local/plugin:/var/www/html/wp-content/plugins/test-name',
86
+ '/path/to/local/theme:/var/www/html/wp-content/themes/test-theme',
87
+ ];
88
+ expect( devVolumes ).toEqual( expect.arrayContaining( localSources ) );
89
+
90
+ localSources = [
91
+ '/path/to/wp-plugins:/var/www/html/wp-content/plugins',
92
+ '/path/tests-WordPress-PHPUnit/tests/phpunit:/wordpress-phpunit',
93
+ 'tests-user-home:/home/test',
94
+ '/path/to/local/plugin:/var/www/html/wp-content/plugins/test-name',
95
+ '/path/to/local/theme:/var/www/html/wp-content/themes/test-theme',
96
+ ];
97
+ expect( testsVolumes ).toEqual(
98
+ expect.arrayContaining( localSources )
99
+ );
100
+ } );
101
+
102
+ it( 'should create "wordpress" and "tests-wordpress" volumes if they are needed by containers', () => {
103
+ // CONFIG has no coreSource entry, so there are no core sources on the
104
+ // local filesystem, so a volume should be created to contain core
105
+ // sources.
106
+ const dockerConfig = buildDockerComposeConfig( {
107
+ workDirectoryPath: '/path',
108
+ env: { development: CONFIG, tests: CONFIG },
109
+ } );
110
+
111
+ expect( dockerConfig.volumes.wordpress ).not.toBe( undefined );
112
+ expect( dockerConfig.volumes[ 'tests-wordpress' ] ).not.toBe(
113
+ undefined
114
+ );
115
+ } );
116
+
117
+ it( 'should NOT create "wordpress" and "tests-wordpress" volumes if they are not needed by containers', () => {
118
+ const envConfig = {
119
+ ...CONFIG,
120
+ coreSource: {
121
+ path: '/some/random/path',
122
+ local: true,
123
+ },
124
+ };
125
+
126
+ const dockerConfig = buildDockerComposeConfig( {
127
+ workDirectoryPath: '/path',
128
+ env: { development: envConfig, tests: envConfig },
129
+ } );
130
+
131
+ expect( dockerConfig.volumes.wordpress ).toBe( undefined );
132
+ expect( dockerConfig.volumes[ 'tests-wordpress' ] ).toBe( undefined );
133
+ } );
134
+ } );
@@ -0,0 +1,154 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const { readFile, writeFile } = require( 'fs' ).promises;
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ const {
11
+ didCacheChange,
12
+ setCache,
13
+ getCache,
14
+ getCacheFile,
15
+ } = require( '../cache' );
16
+
17
+ jest.mock( 'fs', () => ( {
18
+ promises: {
19
+ readFile: jest.fn(),
20
+ writeFile: jest.fn(),
21
+ },
22
+ } ) );
23
+
24
+ const cacheOptions = {
25
+ workDirectoryPath: '/a/b/c',
26
+ };
27
+
28
+ function deleteCacheFile() {
29
+ readFile.mockImplementation( () => Promise.reject( 'No file!' ) );
30
+ }
31
+
32
+ function setCacheFile( data ) {
33
+ readFile.mockImplementation( () =>
34
+ Promise.resolve( JSON.stringify( data ) )
35
+ );
36
+ }
37
+
38
+ function setupWriteFile() {
39
+ writeFile.mockImplementation( ( fileName, data ) => {
40
+ readFile.mockClear();
41
+ readFile.mockImplementation( () => Promise.resolve( data ) );
42
+ } );
43
+ }
44
+
45
+ describe( 'cache file', () => {
46
+ beforeEach( () => {
47
+ jest.clearAllMocks();
48
+ } );
49
+
50
+ describe( 'didCacheChange', () => {
51
+ it( 'returns true if the existing cache value is different', async () => {
52
+ setCacheFile( { test: 'test1' } );
53
+ const result = await didCacheChange( 'test', 'nope', cacheOptions );
54
+ expect( result ).toBe( true );
55
+ } );
56
+ it( 'returns false if the existing cache value is the same', async () => {
57
+ setCacheFile( { test: 'test1' } );
58
+ const result = await didCacheChange(
59
+ 'test',
60
+ 'test1',
61
+ cacheOptions
62
+ );
63
+ expect( result ).toBe( false );
64
+ } );
65
+ it( 'returns true if the existing cache value does not exist', async () => {
66
+ expect.assertions( 2 );
67
+
68
+ setCacheFile( { howdy: 'test1' } );
69
+ const result = await didCacheChange( 'test', 'nope', cacheOptions );
70
+ expect( result ).toBe( true );
71
+
72
+ deleteCacheFile();
73
+ const result2 = await didCacheChange(
74
+ 'test',
75
+ 'nope',
76
+ cacheOptions
77
+ );
78
+ expect( result2 ).toBe( true );
79
+ } );
80
+ } );
81
+
82
+ describe( 'setCache', () => {
83
+ it( 'saves a new cache value to the file', async () => {
84
+ setupWriteFile();
85
+ await setCache( 'test', 'abc', cacheOptions );
86
+ const result = await getCacheFile( cacheOptions );
87
+ expect( result ).toEqual( { test: 'abc' } );
88
+ } );
89
+ it( 'overwrites an existing key', async () => {
90
+ expect.assertions( 2 );
91
+ setCacheFile( { test: 'abc' } );
92
+ const result = await getCacheFile( cacheOptions );
93
+ expect( result ).toEqual( { test: 'abc' } );
94
+
95
+ await setCache( 'test', '123', cacheOptions );
96
+ const result2 = await getCacheFile( cacheOptions );
97
+ expect( result2 ).toEqual( { test: '123' } );
98
+ } );
99
+ it( 'does not overwrite other keys', async () => {
100
+ setCacheFile( { test: 'abc' } );
101
+
102
+ await setCache( 'test2', 1234, cacheOptions );
103
+ const result = await getCacheFile( cacheOptions );
104
+ expect( result ).toEqual( { test: 'abc', test2: 1234 } );
105
+ } );
106
+ } );
107
+
108
+ describe( 'getCache', () => {
109
+ it( 'returns the cache value associated with the key', async () => {
110
+ const value = 'test1';
111
+ setCacheFile( { test: value } );
112
+ const result = await getCache( 'test', cacheOptions );
113
+ expect( result ).toBe( value );
114
+ } );
115
+ it( 'returns undefined if there is no existing value', async () => {
116
+ setCacheFile( { anotherValue: 'hello' } );
117
+ const result = await getCache( 'test', cacheOptions );
118
+ expect( result ).toBe( undefined );
119
+ } );
120
+ it( 'returns undefined if the file does not exist', async () => {
121
+ deleteCacheFile();
122
+ const result = await getCache( 'test', cacheOptions );
123
+ expect( result ).toBe( undefined );
124
+ } );
125
+ } );
126
+
127
+ describe( 'getCacheFile', () => {
128
+ it( 'returns the stored JSON data as an Object', async () => {
129
+ const testData = {
130
+ a: 'test',
131
+ b: 1,
132
+ c: true,
133
+ d: null,
134
+ e: {
135
+ foo: 'test2',
136
+ },
137
+ };
138
+ setCacheFile( testData );
139
+
140
+ const result = await getCacheFile( cacheOptions );
141
+ expect( result ).toEqual( testData );
142
+ } );
143
+ it( 'returns an empty object if the file does not exist', async () => {
144
+ deleteCacheFile();
145
+ const result = await getCacheFile( cacheOptions );
146
+ expect( result ).toEqual( {} );
147
+ } );
148
+ it( 'returns an empty object if the file is invalid', async () => {
149
+ readFile.mockImplementation( () => Promise.resolve( '{' ) );
150
+ const result = await getCacheFile( cacheOptions );
151
+ expect( result ).toEqual( {} );
152
+ } );
153
+ } );
154
+ } );