@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,363 @@
1
+ 'use strict';
2
+ /**
3
+ * Internal dependencies
4
+ */
5
+ const {
6
+ ValidationError,
7
+ checkString,
8
+ checkPort,
9
+ checkStringArray,
10
+ checkObjectWithValues,
11
+ checkVersion,
12
+ checkValidURL,
13
+ } = require( '../validate-config' );
14
+
15
+ describe( 'validate-config', () => {
16
+ describe( 'checkString', () => {
17
+ it( 'throws when not a string', () => {
18
+ expect( () => checkString( 'test.json', 'test', 1234 ) ).toThrow(
19
+ new ValidationError(
20
+ 'Invalid test.json: "test" must be a string.'
21
+ )
22
+ );
23
+ } );
24
+
25
+ it( 'passes for string', () => {
26
+ expect( () =>
27
+ checkString( 'test.json', 'test', 'test' )
28
+ ).not.toThrow();
29
+ } );
30
+ } );
31
+
32
+ describe( 'checkPort', () => {
33
+ it( 'throws when not a number', () => {
34
+ expect( () => checkPort( 'test.json', 'test', 'test' ) ).toThrow(
35
+ new ValidationError(
36
+ 'Invalid test.json: "test" must be an integer.'
37
+ )
38
+ );
39
+
40
+ expect( () =>
41
+ checkPort( 'test.json', 'test', { test: 'test' } )
42
+ ).toThrow(
43
+ new ValidationError(
44
+ 'Invalid test.json: "test" must be an integer.'
45
+ )
46
+ );
47
+ } );
48
+
49
+ it( 'throws when port out of range', () => {
50
+ expect( () => checkPort( 'test.json', 'test', -1 ) ).toThrow(
51
+ new ValidationError(
52
+ 'Invalid test.json: "test" must be a valid port.'
53
+ )
54
+ );
55
+
56
+ expect( () => checkPort( 'test.json', 'test', 99999999 ) ).toThrow(
57
+ new ValidationError(
58
+ 'Invalid test.json: "test" must be a valid port.'
59
+ )
60
+ );
61
+ } );
62
+
63
+ it( 'passes for valid port', () => {
64
+ expect( () =>
65
+ checkPort( 'test.json', 'test', 8888 )
66
+ ).not.toThrow();
67
+ } );
68
+ } );
69
+
70
+ describe( 'checkStringArray', () => {
71
+ it( 'throws when not an array', () => {
72
+ expect( () =>
73
+ checkStringArray( 'test.json', 'test', 'test' )
74
+ ).toThrow(
75
+ new ValidationError(
76
+ 'Invalid test.json: "test" must be an array.'
77
+ )
78
+ );
79
+
80
+ expect( () =>
81
+ checkStringArray( 'test.json', 'test', { test: 'test' } )
82
+ ).toThrow(
83
+ new ValidationError(
84
+ 'Invalid test.json: "test" must be an array.'
85
+ )
86
+ );
87
+ } );
88
+
89
+ it( 'throws when array contains non-strings', () => {
90
+ expect( () =>
91
+ checkStringArray( 'test.json', 'test', [ 12 ] )
92
+ ).toThrow(
93
+ new ValidationError(
94
+ 'Invalid test.json: "test" must be an array of strings.'
95
+ )
96
+ );
97
+
98
+ expect( () =>
99
+ checkStringArray( 'test.json', 'test', [ 'test', 12 ] )
100
+ ).toThrow(
101
+ new ValidationError(
102
+ 'Invalid test.json: "test" must be an array of strings.'
103
+ )
104
+ );
105
+ } );
106
+
107
+ it( 'passes for string arrays', () => {
108
+ expect( () =>
109
+ checkStringArray( 'test.json', 'test', [] )
110
+ ).not.toThrow();
111
+ expect( () =>
112
+ checkStringArray( 'test.json', 'test', [ 'test' ] )
113
+ ).not.toThrow();
114
+ } );
115
+ } );
116
+
117
+ describe( 'checkObjectWithValues', () => {
118
+ it( 'throws when not an object', () => {
119
+ expect( () =>
120
+ checkObjectWithValues( 'test.json', 'test', 'test', [], false )
121
+ ).toThrow(
122
+ new ValidationError(
123
+ 'Invalid test.json: "test" must be an object.'
124
+ )
125
+ );
126
+
127
+ expect( () =>
128
+ checkObjectWithValues(
129
+ 'test.json',
130
+ 'test',
131
+ [ 'test' ],
132
+ [],
133
+ false
134
+ )
135
+ ).toThrow(
136
+ new ValidationError(
137
+ 'Invalid test.json: "test" must be an object.'
138
+ )
139
+ );
140
+ } );
141
+
142
+ it( 'throws when no allowed types are given', () => {
143
+ expect( () =>
144
+ checkObjectWithValues(
145
+ 'test.json',
146
+ 'test',
147
+ { test: 'test' },
148
+ [],
149
+ false
150
+ )
151
+ ).toThrow(
152
+ new ValidationError(
153
+ 'Invalid test.json: "test.test" must be of type: .'
154
+ )
155
+ );
156
+ } );
157
+
158
+ it( 'throws when type is not allowed', () => {
159
+ expect( () =>
160
+ checkObjectWithValues(
161
+ 'test.json',
162
+ 'test',
163
+ { test: 'test' },
164
+ [ 'number' ],
165
+ false
166
+ )
167
+ ).toThrow(
168
+ new ValidationError(
169
+ 'Invalid test.json: "test.test" must be of type: number.'
170
+ )
171
+ );
172
+
173
+ expect( () =>
174
+ checkObjectWithValues(
175
+ 'test.json',
176
+ 'test',
177
+ { test: 1 },
178
+ [ 'string' ],
179
+ false
180
+ )
181
+ ).toThrow(
182
+ new ValidationError(
183
+ 'Invalid test.json: "test.test" must be of type: string.'
184
+ )
185
+ );
186
+
187
+ expect( () =>
188
+ checkObjectWithValues(
189
+ 'test.json',
190
+ 'test',
191
+ { test: [ 'test' ] },
192
+ [ 'object', 'string' ],
193
+ false
194
+ )
195
+ ).toThrow(
196
+ new ValidationError(
197
+ 'Invalid test.json: "test.test" must be of type: object or string.'
198
+ )
199
+ );
200
+
201
+ expect( () =>
202
+ checkObjectWithValues(
203
+ 'test.json',
204
+ 'test',
205
+ { test: null },
206
+ [ 'object' ],
207
+ true
208
+ )
209
+ ).toThrow(
210
+ new ValidationError(
211
+ 'Invalid test.json: "test.test" must be of type: object.'
212
+ )
213
+ );
214
+ } );
215
+
216
+ it( 'passes when type is allowed', () => {
217
+ expect( () =>
218
+ checkObjectWithValues(
219
+ 'test.json',
220
+ 'test',
221
+ { test: 'test' },
222
+ [ 'string' ],
223
+ false
224
+ )
225
+ ).not.toThrow();
226
+ expect( () =>
227
+ checkObjectWithValues(
228
+ 'test.json',
229
+ 'test',
230
+ { test: '' },
231
+ [ 'string' ],
232
+ true
233
+ )
234
+ ).not.toThrow();
235
+ expect( () =>
236
+ checkObjectWithValues(
237
+ 'test.json',
238
+ 'test',
239
+ { test: 1 },
240
+ [ 'number' ],
241
+ false
242
+ )
243
+ ).not.toThrow();
244
+ expect( () =>
245
+ checkObjectWithValues(
246
+ 'test.json',
247
+ 'test',
248
+ { test: { nested: 'test' } },
249
+ [ 'object' ],
250
+ false
251
+ )
252
+ ).not.toThrow();
253
+ expect( () =>
254
+ checkObjectWithValues(
255
+ 'test.json',
256
+ 'test',
257
+ { test: [ 'test' ] },
258
+ [ 'array' ],
259
+ false
260
+ )
261
+ ).not.toThrow();
262
+ expect( () =>
263
+ checkObjectWithValues(
264
+ 'test.json',
265
+ 'test',
266
+ { test: null },
267
+ [ 'null' ],
268
+ false
269
+ )
270
+ ).not.toThrow();
271
+ } );
272
+ } );
273
+
274
+ describe( 'checkVersion', () => {
275
+ it( 'throws for invalid input', () => {
276
+ expect( () => checkVersion( 'test.json', 'test', 'test' ) ).toThrow(
277
+ new ValidationError(
278
+ 'Invalid test.json: "test" must be a string of the format "X", "X.X", or "X.X.X".'
279
+ )
280
+ );
281
+
282
+ expect( () => checkVersion( 'test.json', 'test', 123 ) ).toThrow(
283
+ new ValidationError(
284
+ 'Invalid test.json: "test" must be a string.'
285
+ )
286
+ );
287
+ } );
288
+
289
+ it( 'passes for different version formats', () => {
290
+ expect( () =>
291
+ checkVersion( 'test.json', 'test', '1' )
292
+ ).not.toThrow();
293
+ expect( () =>
294
+ checkVersion( 'test.json', 'test', '1.1' )
295
+ ).not.toThrow();
296
+ expect( () =>
297
+ checkVersion( 'test.json', 'test', '1.1.1' )
298
+ ).not.toThrow();
299
+ expect( () =>
300
+ checkVersion( 'test.json', 'test', '15.7.2' )
301
+ ).not.toThrow();
302
+ expect( () =>
303
+ checkVersion( 'test.json', 'test', '26634543' )
304
+ ).not.toThrow();
305
+ } );
306
+ } );
307
+
308
+ describe( 'checkValidURL', () => {
309
+ it( 'throws for invaid URLs', () => {
310
+ expect( () =>
311
+ checkValidURL( 'test.json', 'test', 'localhost' )
312
+ ).toThrow(
313
+ new ValidationError(
314
+ 'Invalid test.json: "test" must be a valid URL.'
315
+ )
316
+ );
317
+
318
+ expect( () => checkValidURL( 'test.json', 'test', '' ) ).toThrow(
319
+ new ValidationError(
320
+ 'Invalid test.json: "test" must be a valid URL.'
321
+ )
322
+ );
323
+
324
+ expect( () => checkValidURL( 'test.json', 'test', 123 ) ).toThrow(
325
+ new ValidationError(
326
+ 'Invalid test.json: "test" must be a valid URL.'
327
+ )
328
+ );
329
+ } );
330
+
331
+ it( 'passes for valid URLs', () => {
332
+ expect( () =>
333
+ checkValidURL( 'test.json', 'test', 'http://test.com' )
334
+ ).not.toThrow();
335
+ expect( () =>
336
+ checkValidURL( 'test.json', 'test', 'https://test.com' )
337
+ ).not.toThrow();
338
+ expect( () =>
339
+ checkValidURL( 'test.json', 'test', 'http://test' )
340
+ ).not.toThrow();
341
+ expect( () =>
342
+ checkValidURL(
343
+ 'test.json',
344
+ 'test',
345
+ 'http://test/test?test=test'
346
+ )
347
+ ).not.toThrow();
348
+ expect( () =>
349
+ checkValidURL( 'test.json', 'test', 'http://test.co.uk' )
350
+ ).not.toThrow();
351
+ expect( () =>
352
+ checkValidURL( 'test.json', 'test', 'https://test.co.uk:8888' )
353
+ ).not.toThrow();
354
+ expect( () =>
355
+ checkValidURL(
356
+ 'test.json',
357
+ 'test',
358
+ 'http://test.co.uk:8888/test?test=test#test'
359
+ )
360
+ ).not.toThrow();
361
+ } );
362
+ } );
363
+ } );
@@ -1,8 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * @typedef {import('./config').WPServiceConfig} WPServiceConfig
5
- * @typedef {import('./config').WPSource} WPSource
4
+ * @typedef {import('./parse-source-string').WPSource} WPSource
6
5
  */
7
6
 
8
7
  /**
@@ -12,105 +11,171 @@
12
11
  class ValidationError extends Error {}
13
12
 
14
13
  /**
15
- * Validates a config object by throwing a ValidationError if any of its properties
16
- * do not match the required format.
14
+ * Validates that the value is a string.
17
15
  *
18
- * @param {Object} config A config object to validate.
19
- * @param {?string} envLocation Identifies if the error occurred in a specific environment property.
20
- * @return {Object} The passed config object with no modifications.
16
+ * @param {string} configFile The configuration file we're validating.
17
+ * @param {string} configKey The configuration key we're validating.
18
+ * @param {number} value The value to check.
21
19
  */
22
- function validateConfig( config, envLocation ) {
23
- const envPrefix = envLocation ? `env.${ envLocation }.` : '';
24
- if ( config.core !== null && typeof config.core !== 'string' ) {
20
+ function checkString( configFile, configKey, value ) {
21
+ if ( typeof value !== 'string' ) {
25
22
  throw new ValidationError(
26
- `Invalid .wp-env.json: "${ envPrefix }core" must be null or a string.`
23
+ `Invalid ${ configFile }: "${ configKey }" must be a string.`
27
24
  );
28
25
  }
26
+ }
29
27
 
30
- if (
31
- ! Array.isArray( config.plugins ) ||
32
- config.plugins.some( ( plugin ) => typeof plugin !== 'string' )
33
- ) {
28
+ /**
29
+ * Validates the port and throws if it isn't valid.
30
+ *
31
+ * @param {string} configFile The configuration file we're validating.
32
+ * @param {string} configKey The configuration key we're validating.
33
+ * @param {number} port The port to check.
34
+ */
35
+ function checkPort( configFile, configKey, port ) {
36
+ if ( ! Number.isInteger( port ) ) {
34
37
  throw new ValidationError(
35
- `Invalid .wp-env.json: "${ envPrefix }plugins" must be an array of strings.`
38
+ `Invalid ${ configFile }: "${ configKey }" must be an integer.`
36
39
  );
37
40
  }
38
41
 
39
- if (
40
- ! Array.isArray( config.themes ) ||
41
- config.themes.some( ( theme ) => typeof theme !== 'string' )
42
- ) {
42
+ if ( port < 0 || port > 65535 ) {
43
43
  throw new ValidationError(
44
- `Invalid .wp-env.json: "${ envPrefix }themes" must be an array of strings.`
44
+ `Invalid ${ configFile }: "${ configKey }" must be a valid port.`
45
45
  );
46
46
  }
47
+ }
47
48
 
48
- if ( ! Number.isInteger( config.port ) ) {
49
+ /**
50
+ * Validates the array and throws if it isn't valid.
51
+ *
52
+ * @param {string} configFile The config file we're validating.
53
+ * @param {string} configKey The configuration key we're validating.
54
+ * @param {string[]} array The array that we're checking.
55
+ */
56
+ function checkStringArray( configFile, configKey, array ) {
57
+ if ( ! Array.isArray( array ) ) {
49
58
  throw new ValidationError(
50
- `Invalid .wp-env.json: "${ envPrefix }port" must be an integer.`
59
+ `Invalid ${ configFile }: "${ configKey }" must be an array.`
51
60
  );
52
61
  }
53
62
 
54
- if ( typeof config.config !== 'object' ) {
63
+ if ( array.some( ( value ) => typeof value !== 'string' ) ) {
55
64
  throw new ValidationError(
56
- `Invalid .wp-env.json: "${ envPrefix }config" must be an object.`
65
+ `Invalid ${ configFile }: "${ configKey }" must be an array of strings.`
57
66
  );
58
67
  }
68
+ }
59
69
 
60
- if ( typeof config.mappings !== 'object' ) {
70
+ /**
71
+ * Validates the object and throws if it isn't valid.
72
+ *
73
+ * @param {string} configFile The config file we're validating.
74
+ * @param {string} configKey The configuration key we're validating.
75
+ * @param {string[]} obj The object that we're checking.
76
+ * @param {string[]} allowTypes The types that are allowed.
77
+ * @param {boolean} allowEmpty Indicates whether or not empty values are allowed.
78
+ */
79
+ function checkObjectWithValues(
80
+ configFile,
81
+ configKey,
82
+ obj,
83
+ allowTypes,
84
+ allowEmpty
85
+ ) {
86
+ if ( allowTypes === undefined ) {
87
+ allowTypes = [];
88
+ }
89
+
90
+ if ( typeof obj !== 'object' || Array.isArray( obj ) ) {
61
91
  throw new ValidationError(
62
- `Invalid .wp-env.json: "${ envPrefix }mappings" must be an object.`
92
+ `Invalid ${ configFile }: "${ configKey }" must be an object.`
63
93
  );
64
94
  }
65
95
 
66
- for ( const [ wpDir, localDir ] of Object.entries( config.mappings ) ) {
67
- if ( ! localDir || typeof localDir !== 'string' ) {
96
+ for ( const key in obj ) {
97
+ // Some values need to be uniquely validated.
98
+ switch ( obj[ key ] ) {
99
+ case null:
100
+ case undefined: {
101
+ break;
102
+ }
103
+
104
+ default: {
105
+ if ( ! obj[ key ] && ! allowEmpty ) {
106
+ throw new ValidationError(
107
+ `Invalid ${ configFile }: "${ configKey }.${ key }" must not be empty.`
108
+ );
109
+ }
110
+ }
111
+ }
112
+
113
+ // Some types need to be uniquely identified.
114
+ let type;
115
+ if ( obj[ key ] === undefined ) {
116
+ type = 'undefined';
117
+ } else if ( obj[ key ] === null ) {
118
+ type = 'null';
119
+ } else if ( Array.isArray( obj[ key ] ) ) {
120
+ type = 'array';
121
+ } else {
122
+ type = typeof obj[ key ];
123
+ }
124
+
125
+ if ( ! allowTypes.includes( type ) ) {
68
126
  throw new ValidationError(
69
- `Invalid .wp-env.json: "${ envPrefix }mappings.${ wpDir }" should be a string.`
127
+ `Invalid ${ configFile }: "${ configKey }.${ key }" must be of type: ${ allowTypes.join(
128
+ ' or '
129
+ ) }.`
70
130
  );
71
131
  }
72
132
  }
133
+ }
73
134
 
74
- if (
75
- config.phpVersion &&
76
- ! (
77
- typeof config.phpVersion === 'string' &&
78
- config.phpVersion.length === 3
79
- )
80
- ) {
135
+ /**
136
+ * Validates the version and throws if it isn't valid.
137
+ *
138
+ * @param {string} configFile The config file we're validating.
139
+ * @param {string} configKey The configuration key we're validating.
140
+ * @param {string} version The version that we're checking.
141
+ */
142
+ function checkVersion( configFile, configKey, version ) {
143
+ if ( typeof version !== 'string' ) {
81
144
  throw new ValidationError(
82
- `Invalid .wp-env.json: "${ envPrefix }phpVersion" must be a string of the format "0.0".`
145
+ `Invalid ${ configFile }: "${ configKey }" must be a string.`
83
146
  );
84
147
  }
85
148
 
86
- checkValidURL( envPrefix, config.config, 'WP_SITEURL' );
87
- checkValidURL( envPrefix, config.config, 'WP_HOME' );
88
-
89
- return config;
149
+ if ( ! version.match( /[0-9]+(?:\.[0-9]+)*/ ) ) {
150
+ throw new ValidationError(
151
+ `Invalid ${ configFile }: "${ configKey }" must be a string of the format "X", "X.X", or "X.X.X".`
152
+ );
153
+ }
90
154
  }
91
155
 
92
156
  /**
93
- * Validates the input and throws if it isn't a valid URL.
157
+ * Validates the url and throws if it isn't valid.
94
158
  *
95
- * @param {string} envPrefix The environment we're validating.
96
- * @param {Object} config The configuration object we're looking at.
97
- * @param {string} configKey The configuration key we're validating.
159
+ * @param {string} configFile The config file we're validating.
160
+ * @param {string} configKey The configuration key we're validating.
161
+ * @param {string} url The URL that we're checking.
98
162
  */
99
- function checkValidURL( envPrefix, config, configKey ) {
100
- if ( config[ configKey ] === undefined ) {
101
- return;
102
- }
103
-
163
+ function checkValidURL( configFile, configKey, url ) {
104
164
  try {
105
- new URL( config[ configKey ] );
165
+ new URL( url );
106
166
  } catch {
107
167
  throw new ValidationError(
108
- `Invalid .wp-env.json: "${ envPrefix }config.${ configKey }" must be a valid URL.`
168
+ `Invalid ${ configFile }: "${ configKey }" must be a valid URL.`
109
169
  );
110
170
  }
111
171
  }
112
172
 
113
173
  module.exports = {
114
- validateConfig,
115
174
  ValidationError,
175
+ checkString,
176
+ checkPort,
177
+ checkStringArray,
178
+ checkObjectWithValues,
179
+ checkVersion,
180
+ checkValidURL,
116
181
  };
package/lib/env.js CHANGED
@@ -3,9 +3,11 @@
3
3
  * Internal dependencies
4
4
  */
5
5
  const { ValidationError } = require( './config' );
6
+ const { LifecycleScriptError } = require( './execute-lifecycle-script' );
6
7
  const commands = require( './commands' );
7
8
 
8
9
  module.exports = {
9
10
  ...commands,
10
11
  ValidationError,
12
+ LifecycleScriptError,
11
13
  };
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const { exec } = require( 'child_process' );
6
+
7
+ /**
8
+ * @typedef {import('./config').WPConfig} WPConfig
9
+ */
10
+
11
+ /**
12
+ * Error subtype which indicates that the lifecycle script failed.
13
+ */
14
+ class LifecycleScriptError extends Error {
15
+ constructor( event, stderr ) {
16
+ super( `${ event } Error:\n${ stderr }` );
17
+
18
+ this.event = event;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Executes any defined life cycle script.
24
+ *
25
+ * @param {string} event The lifecycle event to run the script for.
26
+ * @param {WPConfig} config The config object to use.
27
+ * @param {Object} spinner A CLI spinner which indciates progress.
28
+ *
29
+ * @return {Promise} Resolves when the script has completed and rejects when there is an error.
30
+ */
31
+ function executeLifecycleScript( event, config, spinner ) {
32
+ if ( ! config.lifecycleScripts[ event ] ) {
33
+ return Promise.resolve();
34
+ }
35
+
36
+ return new Promise( ( resolve, reject ) => {
37
+ // We're going to append the script output to the spinner while it's executing.
38
+ const spinnerMessage = `Executing ${ event } Script`;
39
+ spinner.text = spinnerMessage;
40
+
41
+ // Execute the script asynchronously so that it won't block the spinner.
42
+ const childProc = exec( config.lifecycleScripts[ event ], {
43
+ encoding: 'utf-8',
44
+ stdio: 'pipe',
45
+ env: process.env,
46
+ } );
47
+
48
+ // Collect all of the output so that we can make use of it.
49
+ let output = '';
50
+ childProc.stdout.on( 'data', ( data ) => {
51
+ output += data;
52
+
53
+ // Keep the spinner updated with the command output.
54
+ spinner.text = `${ spinnerMessage }\n${ output }`;
55
+ } );
56
+
57
+ // Listen for any error output so we can display it if the command fails.
58
+ let error = '';
59
+ childProc.stderr.on( 'data', ( data ) => {
60
+ error += data;
61
+ } );
62
+
63
+ // Pass any process creation errors directly up.
64
+ childProc.on( 'error', reject );
65
+
66
+ // Handle the completion of the command based on whether it was successful or not.
67
+ childProc.on( 'exit', ( code ) => {
68
+ if ( code === 0 ) {
69
+ // Keep the output of the command in debug mode.
70
+ if ( config.debug ) {
71
+ spinner.info( `${ event } Script:\n${ output.trimEnd() }` );
72
+ }
73
+
74
+ resolve();
75
+ return;
76
+ }
77
+
78
+ reject( new LifecycleScriptError( event, error.trimEnd() ) );
79
+ } );
80
+ } );
81
+ }
82
+
83
+ module.exports = {
84
+ LifecycleScriptError,
85
+ executeLifecycleScript,
86
+ };