@wordpress/env 6.0.0 → 7.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 (54) hide show
  1. package/README.md +109 -43
  2. package/lib/build-docker-compose-config.js +67 -96
  3. package/lib/cache.js +1 -0
  4. package/lib/cli.js +16 -3
  5. package/lib/commands/clean.js +14 -1
  6. package/lib/commands/destroy.js +12 -37
  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 +59 -3
  11. package/lib/commands/start.js +30 -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 +86 -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 +104 -0
  21. package/lib/config/parse-config.js +418 -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 +271 -0
  26. package/lib/config/test/add-or-replace-port.js +29 -1
  27. package/lib/config/test/config-integration.js +143 -0
  28. package/lib/config/test/get-cache-directory.js +57 -0
  29. package/lib/config/test/merge-configs.js +111 -0
  30. package/lib/config/test/parse-config.js +342 -0
  31. package/lib/config/test/parse-source-string.js +154 -0
  32. package/lib/config/test/post-process-config.js +295 -0
  33. package/lib/config/test/read-raw-config-file.js +19 -63
  34. package/lib/config/test/validate-config.js +305 -0
  35. package/lib/config/validate-config.js +93 -54
  36. package/lib/env.js +2 -0
  37. package/lib/execute-after-setup.js +51 -0
  38. package/lib/get-host-user.js +31 -0
  39. package/lib/init-config.js +181 -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-after-setup.js +66 -0
  48. package/lib/test/md5.js +35 -0
  49. package/lib/test/parse-xdebug-mode.js +41 -0
  50. package/lib/wordpress.js +4 -19
  51. package/package.json +2 -2
  52. package/lib/config/config.js +0 -353
  53. package/lib/config/test/__snapshots__/config.js.snap +0 -83
  54. package/lib/config/test/config.js +0 -1241
@@ -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,189 @@ 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.
147
192
 
148
- if ( config.xdebug !== 'off' ) {
149
- const usingCompatiblePhp = checkXdebugPhpCompatibility( config );
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
150
199
 
151
- if ( usingCompatiblePhp ) {
152
- shouldInstallXdebug = true;
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_USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers`;
206
+ break;
207
+ }
208
+ case 'cli': {
209
+ dockerFileContent += `
210
+ RUN apk update
211
+ RUN apk --no-cache add $PHPIZE_DEPS && touch /usr/local/etc/php/php.ini
212
+ RUN apk --no-cache add sudo linux-headers
213
+ RUN echo "$HOST_USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers`;
214
+ break;
215
+ }
216
+ default: {
217
+ throw new Error( `Invalid service "${ service }" given` );
153
218
  }
154
219
  }
155
220
 
156
- return `FROM ${ image }
221
+ dockerFileContent += getXdebugConfig(
222
+ config.xdebug,
223
+ config.env[ env ].phpVersion
224
+ );
157
225
 
158
- RUN apt-get -qy install $PHPIZE_DEPS && touch /usr/local/etc/php/php.ini
159
- ${ shouldInstallXdebug ? installXdebug( config.xdebug ) : '' }
160
- `;
226
+ // Add better PHP settings.
227
+ dockerFileContent += `
228
+ RUN echo 'upload_max_filesize = 1G' >> /usr/local/etc/php/php.ini
229
+ RUN echo 'post_max_size = 1G' >> /usr/local/etc/php/php.ini`;
230
+
231
+ // Make sure Composer is available for use in all services.
232
+ dockerFileContent += `
233
+ RUN curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php
234
+ 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;"
235
+ RUN php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer
236
+ RUN rm /tmp/composer-setup.php`;
237
+
238
+ // Install any Composer packages we might need globally.
239
+ // Make sure to do this as the user and ensure the binaries are available in the $PATH.
240
+ dockerFileContent += `
241
+ USER $HOST_USERNAME
242
+ ENV PATH="\${PATH}:/home/$HOST_USERNAME/.composer/vendor/bin"
243
+ RUN composer global require --dev yoast/phpunit-polyfills:"^1.0"
244
+ USER root`;
245
+
246
+ return dockerFileContent;
161
247
  }
162
248
 
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"';
249
+ /**
250
+ * Gets the Xdebug config based on the options in the config object.
251
+ *
252
+ * @param {string} xdebugMode The Xdebug mode set in the config.
253
+ * @param {string} phpVersion The php version set in the environment.
254
+ * @return {string} The Xdebug config -- can be an empty string when it's not used.
255
+ */
256
+ function getXdebugConfig( xdebugMode = 'off', phpVersion ) {
257
+ if ( xdebugMode === 'off' ) {
258
+ return '';
259
+ }
260
+
261
+ let xdebugVersion = 'xdebug';
262
+
263
+ if ( phpVersion ) {
264
+ const versionTokens = phpVersion.split( '.' );
265
+ const majorVer = parseInt( versionTokens[ 0 ] );
266
+ const minorVer = parseInt( versionTokens[ 1 ] );
267
+
268
+ if ( isNaN( majorVer ) || isNaN( minorVer ) ) {
269
+ throw new ValidationError(
270
+ 'Something went wrong when parsing the PHP version.'
271
+ );
272
+ }
273
+
274
+ // Throw an error if someone tries to use Xdebug with an unsupported PHP version.
275
+ // Xdebug 3 only supports 7.2 and higher.
276
+ if ( majorVer < 7 || ( majorVer === 7 && minorVer < 2 ) ) {
277
+ throw new ValidationError(
278
+ `Cannot use XDebug 3 with PHP < 7.2. Your PHP version is ${ phpVersion }.`
279
+ );
280
+ }
281
+
282
+ // For now, we support PHP 7 by installing the final version of Xdebug to
283
+ // support PHP 7 when the environment uses that version. By default, use the
284
+ // latest version.
285
+ if ( majorVer === 7 ) {
286
+ xdebugVersion = 'xdebug-3.1.6';
287
+ }
288
+ }
169
289
 
170
290
  return `
171
- # Install Xdebug:
172
- RUN if [ -z "$(pecl list | grep xdebug)" ] ; then pecl install xdebug ; fi
291
+ RUN if [ -z "$(pecl list | grep ${ xdebugVersion })" ] ; then pecl install ${ xdebugVersion } ; fi
173
292
  RUN docker-php-ext-enable xdebug
174
293
  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
- `;
294
+ RUN echo 'xdebug.mode=${ xdebugMode }' >> /usr/local/etc/php/php.ini
295
+ RUN echo 'xdebug.client_host="host.docker.internal"' >> /usr/local/etc/php/php.ini`;
178
296
  }
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
+ } );