@caweb/cli 1.2.0 → 1.3.1

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 (50) hide show
  1. package/README.md +11 -6
  2. package/bin/caweb +1 -1
  3. package/bin/wp-cli.phar +0 -0
  4. package/commands/a11y.js +74 -0
  5. package/{lib/commands → commands}/blocks/create-block.js +7 -8
  6. package/{lib/commands → commands}/blocks/update-block.js +3 -9
  7. package/commands/build.js +73 -0
  8. package/{lib/commands → commands/env}/destroy.js +15 -22
  9. package/{lib/commands → commands/env}/start.js +41 -27
  10. package/{lib/commands → commands/env}/stop.js +4 -10
  11. package/{lib/commands → commands}/index.js +51 -43
  12. package/commands/serve.js +78 -0
  13. package/commands/sync.js +226 -0
  14. package/{lib/commands → commands}/tasks/update-plugins.js +2 -2
  15. package/commands/test.js +100 -0
  16. package/configs/aceconfig.js +28 -0
  17. package/{lib/configs.js → configs/docker-compose.js} +30 -83
  18. package/configs/webpack.config.js +119 -0
  19. package/configs/wp-env.js +76 -0
  20. package/gen/parser.js +166 -0
  21. package/gen/site-generator.js +111 -0
  22. package/lib/admin.js +1 -1
  23. package/lib/cli.js +106 -64
  24. package/lib/helpers.js +109 -0
  25. package/lib/index.js +53 -0
  26. package/lib/spinner.js +36 -8
  27. package/lib/wordpress/api.js +392 -0
  28. package/lib/{caweb.js → wordpress/caweb.js} +1 -1
  29. package/lib/{divi.js → wordpress/divi.js} +1 -1
  30. package/lib/{download-sources.js → wordpress/download-sources.js} +74 -78
  31. package/lib/wordpress/index.js +19 -0
  32. package/lib/{wordpress.js → wordpress/wordpress.js} +4 -8
  33. package/package.json +41 -27
  34. package/lib/commands/test.js +0 -46
  35. package/lib/utils.js +0 -150
  36. /package/{lib/commands → commands/tasks}/shell.js +0 -0
  37. /package/lib/{options.js → wordpress/options.js} +0 -0
  38. /package/{lib/template → template}/assets/css/popover.css +0 -0
  39. /package/{lib/template → template}/assets/js/popover.js +0 -0
  40. /package/{lib/template → template}/block/edit.js.mustache +0 -0
  41. /package/{lib/template → template}/block/editor.scss.mustache +0 -0
  42. /package/{lib/template → template}/block/index.js.mustache +0 -0
  43. /package/{lib/template → template}/block/save.js.mustache +0 -0
  44. /package/{lib/template → template}/block/style.scss.mustache +0 -0
  45. /package/{lib/template → template}/index.cjs +0 -0
  46. /package/{lib/template → template}/plugin/$slug.php.mustache +0 -0
  47. /package/{lib/template → template}/plugin/core/cdec-api.php.mustache +0 -0
  48. /package/{lib/template → template}/plugin/core/filters.php.mustache +0 -0
  49. /package/{lib/template → template}/plugin/core/functions.php.mustache +0 -0
  50. /package/{lib/template → template}/plugin/inc/renderer.php.mustache +0 -0
package/lib/index.js ADDED
@@ -0,0 +1,53 @@
1
+ import {
2
+ wpPrimary,
3
+ wpGreen,
4
+ wpRed,
5
+ wpYellow,
6
+ withSpinner,
7
+ } from './spinner.js';
8
+
9
+ import {
10
+ runCmd,
11
+ runCLICmds,
12
+ currentPath,
13
+ projectPath,
14
+ appPath
15
+ } from './helpers.js';
16
+
17
+ import {
18
+ activateCAWeb,
19
+ configureCAWeb,
20
+ downloadSources,
21
+ configureDivi,
22
+ isDiviThemeActive,
23
+ configureWordPress,
24
+ isMultisite,
25
+ convertToMultisite,
26
+ generateHTAccess,
27
+ getTaxonomies,
28
+ createTaxonomies
29
+ } from './wordpress/index.js';
30
+
31
+ export {
32
+ currentPath,
33
+ projectPath,
34
+ appPath,
35
+ wpPrimary,
36
+ wpGreen,
37
+ wpRed,
38
+ wpYellow,
39
+ withSpinner,
40
+ runCmd,
41
+ runCLICmds,
42
+ activateCAWeb,
43
+ configureCAWeb,
44
+ downloadSources,
45
+ configureDivi,
46
+ isDiviThemeActive,
47
+ configureWordPress,
48
+ isMultisite,
49
+ convertToMultisite,
50
+ generateHTAccess,
51
+ getTaxonomies,
52
+ createTaxonomies
53
+ }
package/lib/spinner.js CHANGED
@@ -1,5 +1,9 @@
1
+ /**
2
+ * External dependencies
3
+ */
1
4
  import chalk from 'chalk';
2
5
  import ora from 'ora';
6
+ import terminalLink from 'terminal-link';
3
7
 
4
8
  // Colors.
5
9
  const boldWhite = chalk.bold.white;
@@ -18,13 +22,16 @@ const withSpinner =
18
22
  // lets combine arguments with options
19
23
  const cmd = args.pop();
20
24
 
21
- cmd.registeredArguments.forEach(arg => {
22
- // get arg from list.
23
- let v = args.shift();
24
-
25
- // add arg to options.
26
- args[args.length - 1][arg._name] = v;
27
- });
25
+ if( cmd.registeredArguments ){
26
+ cmd.registeredArguments.forEach(arg => {
27
+ // get arg from list.
28
+ let v = args.shift();
29
+
30
+ // add arg to options.
31
+ args[args.length - 1][arg._name] = v;
32
+ });
33
+ }
34
+
28
35
 
29
36
  // add any global options.
30
37
  args[0] = {
@@ -45,7 +52,25 @@ const withSpinner =
45
52
  process.exit( 0 );
46
53
  },
47
54
  ( error ) => {
48
- if( error ){
55
+
56
+ // Axios Error
57
+ if( error &&
58
+ 'object' === typeof error &&
59
+ 'response' in error &&
60
+ 'request' in error
61
+ ){
62
+
63
+ let { data, status } = error.response;
64
+
65
+ if( 401 === status ){
66
+ data.message += `\nPlease check your ${terminalLink('Application Password', 'https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/')}`;
67
+ }
68
+
69
+ spinner.fail( data.message )
70
+ process.exit( 1 );
71
+
72
+
73
+ }else if( error ){
49
74
  // Error is an unknown error. That means there was a bug in our code.
50
75
  spinner.fail(
51
76
  typeof error === 'string' ? error : error.message
@@ -53,10 +78,13 @@ const withSpinner =
53
78
  // Disable reason: Using console.error() means we get a stack trace.
54
79
  console.error( error );
55
80
  process.exit( 1 );
81
+
56
82
  }else{
57
83
  spinner.fail( 'An unknown error occurred.' );
58
84
  process.exit( 1 );
85
+
59
86
  }
87
+
60
88
  }
61
89
  );
62
90
  };
@@ -0,0 +1,392 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import path from 'path';
5
+ import axios from 'axios';
6
+ import terminalLink from 'terminal-link';
7
+
8
+
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ import {
13
+ projectPath,
14
+ runCmd
15
+ } from '../index.js';
16
+
17
+ const endpoint = '/wp-json/wp/v2';
18
+ const requestDelay = 1000; // we wait 1 second between requests
19
+
20
+
21
+ const sleep = (waitTimeInMs) => new Promise(resolve => setTimeout(resolve, waitTimeInMs));
22
+
23
+ function processData( data ){
24
+ /**
25
+ * If wpautop is enabled and data contains Divi shortcodes,
26
+ * WordPress will automatically add an opening/closing p tag that needs to be removed.
27
+ * @link https://developer.wordpress.org/reference/functions/wpautop/
28
+ */
29
+ if( data.includes('et_pb_section') &&
30
+ data.startsWith('<p>') &&
31
+ data.endsWith('</p>\n')
32
+ ){
33
+ data = data.substring(3, data.length - 5)
34
+ }
35
+ return data
36
+ .replace(/(&#8221;)|(&#8243;)|(&#8220;)/g, '') // replace double encoding
37
+ .replace(/(\s{2,})/g, ' ') // replace misc spacing // &#8220;
38
+
39
+ }
40
+
41
+ function processUrlParam( param ){
42
+ return ( Array === typeof param ? param.join(',') : param )
43
+ }
44
+
45
+ /**
46
+ * Make Requests to WordPress Rest API and return different taxonomy results.
47
+ *
48
+ *
49
+ * @async
50
+ * @param {Object} request Data to pass to Axios request.
51
+ * @param {string} [tax='pages'] Taxonomy of the request made. Default pages, available choices: pages,posts,menus,menu-items,settings,media
52
+ * @returns {unknown}
53
+ */
54
+ async function getTaxonomies( request, tax = 'pages' ){
55
+ const fields = request.fields ? request.fields : [
56
+ 'id',
57
+ 'type',
58
+ 'alt_text',
59
+ 'caption',
60
+ 'slug',
61
+ 'title',
62
+ 'source_url',
63
+ 'mime_type',
64
+ 'content',
65
+ 'date',
66
+ 'date_gmt',
67
+ 'status',
68
+ 'featured_media',
69
+ 'comment_status',
70
+ 'ping_status',
71
+ 'template',
72
+ 'format',
73
+ 'categories',
74
+ 'tags',
75
+ 'meta',
76
+ ];
77
+
78
+ let urlParams = [
79
+ 'order=asc'
80
+ ]
81
+
82
+
83
+ // if fields are passed
84
+ if( fields.length && 'all' !== fields ){
85
+ urlParams.push( `_fields=${ fields.join(',')}` );
86
+ }
87
+
88
+
89
+ // if no request is added default to GET
90
+ if( ! request.method ){
91
+ request.method = 'GET'
92
+ }
93
+
94
+ // if parent argument
95
+ if( request.parent ){
96
+ urlParams.push( `parent=` + processUrlParam( request.parent ))
97
+ }
98
+
99
+ // if include argument
100
+ if( request.include ){
101
+ urlParams.push( `include=` + processUrlParam( request.include ))
102
+ }
103
+
104
+ // if menus argument
105
+ if( request.menus ){
106
+ urlParams.push( 'menus=' + processUrlParam( request.menus ) );
107
+ }
108
+
109
+ // if per page argument.
110
+ urlParams.push( 'per_page=' + request.per_page ? request.per_page : '100' )
111
+
112
+
113
+ // if posts_per_page argument.
114
+ urlParams.push( 'posts_per_page=' + request.posts_per_page ? request.posts_per_page : '10' )
115
+
116
+
117
+ let collection = [];
118
+ let results = false;
119
+
120
+ /**
121
+ * Results are capped to 100 max per page
122
+ * in order to return all results, we make the same requests and pass the page parameter.
123
+ * @see https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/
124
+ * @type {number}
125
+ */
126
+ let page = 1;
127
+
128
+ do{
129
+
130
+ results = await axios.request(
131
+ {
132
+ ...request,
133
+ url: `${request.url}${endpoint}/${tax}?` + urlParams.join('&') + `&page=${page}`
134
+ }
135
+ ).then(
136
+ (res) => {
137
+ // if no data or empty data return false.
138
+ if( ! res.data || ( 'object' === typeof res.data && ! Object.entries(res.data).length ) ){
139
+ return false;
140
+ }
141
+
142
+ // add results to collection
143
+ collection = collection.concat( res.data );
144
+ return true
145
+ }).catch(
146
+ (error) => {
147
+ return false
148
+ }
149
+ );
150
+
151
+ // settings taxonomies will always return results regardless of the page parameter.
152
+ // so we return false after the first request
153
+ if( 'settings' === tax ){
154
+ break;
155
+ }
156
+
157
+ page++;
158
+ }while( results )
159
+
160
+
161
+ return collection;
162
+ }
163
+
164
+ async function createTaxonomies( taxData, request, tax = 'pages', spinner ){
165
+ // if no request is added default to POST
166
+ if( ! request.method ){
167
+ request.method = 'POST'
168
+ }
169
+
170
+ let collection = [];
171
+
172
+ for( let obj of taxData ){
173
+ // endpoint url.
174
+ let url = `${request.url}${endpoint}/${tax}`;
175
+ let existingID = false;
176
+
177
+ // in order to maintain ID's, we have to use the cli, the REST API doesn't allow passing the id.
178
+ if( obj.id ){
179
+ // first we check if the ID exist
180
+ let idExists = await axios.request(
181
+ {
182
+ ...request,
183
+ method: 'GET',
184
+ url: `${url}/${ obj.id }`
185
+ }
186
+ )
187
+ .then((res) => { return res.data.id })
188
+ .catch(error => {return false;})
189
+
190
+ // for menus if ID doesn't exist we also check for matching slug
191
+ // if a menu with a matching slug exists we return that id instead.
192
+ if( 'menus' === tax && ! idExists ){
193
+ idExists = await axios.request(
194
+ {
195
+ ...request,
196
+ method: 'GET',
197
+ url: `${url}/?slug=${ obj.slug }`
198
+ }
199
+ )
200
+ .then((res) => { return res.data[0].id })
201
+ .catch(error => {return false;})
202
+
203
+ }
204
+
205
+ /**
206
+ * if the ID doesn't exist we save it so we can update via CLI later on.
207
+ * if it does exist update endpoint url so we update the existing item.
208
+ */
209
+ if( ! idExists ){
210
+ existingID = obj.id;
211
+ }else{
212
+ url = `${url}/${ obj.id }`;
213
+ }
214
+
215
+ // id has to be deleted.
216
+ delete obj.id;
217
+ }
218
+
219
+ // process properties
220
+ if( obj.content && obj.content.rendered ){
221
+ obj.content = processData(obj.content.rendered);
222
+ }
223
+
224
+ if( obj.title && obj.title.rendered ){
225
+ obj.title = processData(obj.title.rendered);
226
+ }
227
+
228
+ if( obj.caption && obj.caption.rendered ){
229
+ obj.caption = processData(obj.caption.rendered);
230
+ }
231
+
232
+ // make WordPress REST API request.
233
+ let results = await axios.request({
234
+ ...request,
235
+ data: 'media' === tax ? createMediaItem(obj) : obj,
236
+ url
237
+ })
238
+ .then( async (res) => { return res.data; } )
239
+
240
+
241
+ /**
242
+ * if the obj had an existing ID that didn't exist
243
+ */
244
+ if( existingID ){
245
+ let post_tbl = 'wp_posts';
246
+ let post_meta_tbl = 'wp_postmeta';
247
+ let term_taxonomy_tbl = 'wp_term_taxonomy';
248
+ let terms_tbl = 'wp_terms';
249
+
250
+ const cmd = [
251
+ path.join(projectPath, 'bin', 'wp-cli.phar'),
252
+ `--ssh=${request.ssh}`,
253
+ '--skip-themes',
254
+ `--url=${new URL(request.url).origin}`
255
+ ];
256
+
257
+ // if taxonomy is page/post/media.
258
+ if( ['pages', 'posts', 'media'].includes(tax) ){
259
+
260
+
261
+ /**
262
+ * Since the REST API doesn't detected trashed posts/pages.
263
+ * We have to do a drop before we can do an update.
264
+ * Since trashed posts/pages stay in the database
265
+ */
266
+ // drop post
267
+ /*await runCmd(
268
+ 'php',
269
+ [
270
+ ...cmd,
271
+ '--skip-column-names',
272
+ `db query 'DELETE FROM ${post_tbl} WHERE ID=${existingID}'`,
273
+ ]
274
+ )
275
+
276
+ // drop post meta.
277
+ await runCmd(
278
+ 'php',
279
+ [
280
+ ...cmd,
281
+ '--skip-column-names',
282
+ `db query 'DELETE FROM ${post_meta_tbl} WHERE post_id=${existingID}'`,
283
+ ]
284
+ )*/
285
+
286
+ // now we can update the new ID with the existing ID.
287
+ await runCmd(
288
+ 'php',
289
+ [
290
+ ...cmd,
291
+ '--skip-column-names',
292
+ `db query 'UPDATE ${post_tbl} SET ID=${existingID} WHERE ID=${results.id}'`,
293
+ ]
294
+ )
295
+ // update post meta.
296
+ await runCmd(
297
+ 'php',
298
+ [
299
+ ...cmd,
300
+ '--skip-column-names',
301
+ `db query 'UPDATE ${post_meta_tbl} SET post_id=${existingID} WHERE post_id=${results.id}'`,
302
+ ]
303
+ )
304
+
305
+ // if taxonomy is menu.
306
+ }else if( 'menus' === tax ){
307
+ // update the terms table.
308
+ await runCmd(
309
+ 'php',
310
+ [
311
+ ...cmd,
312
+ '--skip-column-names',
313
+ `db query 'UPDATE ${terms_tbl} SET term_id=${existingID} WHERE term_id=${results.id}'`,
314
+ ]
315
+ )
316
+
317
+ // update the term taxonomy table.
318
+ await runCmd(
319
+ 'php',
320
+ [
321
+ ...cmd,
322
+ '--skip-column-names',
323
+ `db query 'UPDATE ${term_taxonomy_tbl} SET term_taxonomy_id=${existingID},term_id=${existingID} WHERE term_id=${results.id}'`,
324
+ ]
325
+ )
326
+
327
+ /**
328
+ * Menus are also stored under nav_menu_locations in the the option theme_mods_<theme_name>
329
+ * for each location we patch that nav_menu_locations key with the correct ID
330
+ *
331
+ * @todo remove hardcoding of theme name
332
+ */
333
+ results.locations.map( async (location) => {
334
+ await runCmd(
335
+ 'php',
336
+ [
337
+ ...cmd,
338
+ 'option patch update',
339
+ `theme_mods_CAWeb`,
340
+ 'nav_menu_locations',
341
+ location,
342
+ existingID
343
+ ]
344
+ )
345
+ })
346
+
347
+ }
348
+
349
+ // update the API results ID, back to the existing ID from earlier.
350
+ results.id = existingID;
351
+ }
352
+
353
+ // add results to collection
354
+ collection = collection.concat( results );
355
+
356
+ let p = tax.charAt(0).toUpperCase() + tax.substring(1, tax.length - ( tax.endsWith('s') ? 1 : 0 ) );
357
+
358
+ if( undefined !== results && results.id ){
359
+ spinner.info(`${p}: ${results.id} was created.`);
360
+ }
361
+
362
+ }
363
+
364
+
365
+ }
366
+
367
+ function createMediaItem( media ){
368
+
369
+ let fd = new FormData();
370
+ let featureMediaFile = new File(
371
+ [
372
+ media.data
373
+ ],
374
+ media.source_url.split('/').pop(),
375
+ {
376
+ type: media.mime_type
377
+ }
378
+ );
379
+ fd.append('file', featureMediaFile );
380
+ fd.append('title', media.title);
381
+ fd.append('caption', media.caption);
382
+ fd.append('alt_text', media.alt_text);
383
+ fd.append('date', media.date);
384
+
385
+ return fd;
386
+ }
387
+
388
+
389
+ export {
390
+ getTaxonomies,
391
+ createTaxonomies
392
+ }
@@ -7,7 +7,7 @@ import retry from '@wordpress/env/lib/retry.js';
7
7
  * Internal dependencies
8
8
  */
9
9
  import { CAWEB_OPTIONS } from'./options.js';
10
- import { runCLICmds } from'./utils.js';
10
+ import { runCLICmds } from'../helpers.js';
11
11
  import { isMultisite } from'./wordpress.js';
12
12
  import { configureDivi } from'./divi.js';
13
13
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Internal dependencies
3
3
  */
4
- import { runCLICmds } from './utils.js';
4
+ import { runCLICmds } from '../helpers.js';
5
5
  import { DIVI_OPTIONS } from './options.js';
6
6
 
7
7
 
@@ -29,81 +29,6 @@ const pipeline = util.promisify( stream.pipeline );
29
29
  const extractZip = util.promisify( zip );
30
30
  const rimraf = util.promisify( rim );
31
31
 
32
- /**
33
- * Download CAWeb Resources.
34
- *
35
- * @param {Object} spinner The spinner object to show progress.
36
- * @return {WPConfig} The config object we've loaded.
37
- */
38
- async function downloadSources(
39
- {
40
- spinner,
41
- config
42
- }
43
- ) {
44
- const progresses = {};
45
- const getProgressSetter = ( id ) => ( progress ) => {
46
- progresses[ id ] = progress;
47
- spinner.text =
48
- `Downloading ${id}.\n` +
49
- Object.entries( progresses )
50
- .map(
51
- ( [ key, value ] ) =>
52
- ` - ${ key }: ${ ( value * 100 ).toFixed( 0 ) }/100%`
53
- )
54
- .join( '\n' );
55
- };
56
-
57
- const { workDirectoryPath } = config;
58
- const { development: dev, tests: test } = config.env;
59
-
60
- let pluginDir = path.resolve(workDirectoryPath, 'plugins');
61
- let themeDir = path.resolve(workDirectoryPath, 'themes');
62
-
63
- let sources = dowloadPlugins(pluginDir);
64
-
65
- // Add Divi Theme and plugin to sources.
66
- if( (undefined !== dev.config.ET_USERNAME &&
67
- undefined !== dev.config.ET_API_KEY) ||
68
- (undefined !== test.config.ET_USERNAME &&
69
- undefined !== test.config.ET_API_KEY)
70
- ){
71
- let url = 'https://www.elegantthemes.com/api/api_downloads.php';
72
- let user = undefined !== dev.config.ET_USERNAME ? dev.config.ET_USERNAME : test.config.ET_USERNAME;
73
- let key = undefined !== dev.config.ET_API_KEY ? dev.config.ET_API_KEY : test.config.ET_API_KEY;
74
-
75
- // Add Divi sources.
76
- sources = sources.concat( [
77
- {
78
- basename: 'Divi',
79
- url: `${url}?api_update=1&theme=Divi&api_key=${key}&username=${user}`,
80
- path: path.join(themeDir, 'Divi'),
81
- type: 'zip'
82
- },
83
- {
84
- basename: 'Divi Plugin',
85
- url: `${url}?api_update=1&theme=divi-builder&api_key=${key}&username=${user}`,
86
- path: path.join(pluginDir, 'divi-builder'),
87
- type: 'zip'
88
- }
89
- ]);
90
- }
91
-
92
- // Ensure plugin/theme directory exists for downloading resources.
93
- fs.ensureDir(themeDir);
94
- fs.ensureDir(pluginDir);
95
-
96
- await Promise.all(
97
- sources.map( ( source ) =>
98
- downloadSource( source, {
99
- onProgress: getProgressSetter( source.basename ),
100
- spinner,
101
- } )
102
- )
103
- );
104
-
105
- };
106
-
107
32
  /**
108
33
  * Downloads the given source if necessary. The specific action taken depends
109
34
  * on the source type. The source is downloaded to source.path.
@@ -272,6 +197,77 @@ function dowloadPlugins(pluginDir){
272
197
  ]
273
198
  }
274
199
 
275
- export {
276
- downloadSources
277
- }
200
+ /**
201
+ * Download CAWeb Resources.
202
+ *
203
+ * @param {Object} spinner The spinner object to show progress.
204
+ * @return {WPConfig} The config object we've loaded.
205
+ */
206
+ export async function downloadSources(
207
+ {
208
+ spinner,
209
+ config
210
+ }
211
+ ) {
212
+ const progresses = {};
213
+ const getProgressSetter = ( id ) => ( progress ) => {
214
+ progresses[ id ] = progress;
215
+ spinner.text =
216
+ `Downloading ${id}.\n` +
217
+ Object.entries( progresses )
218
+ .map(
219
+ ( [ key, value ] ) =>
220
+ ` - ${ key }: ${ ( value * 100 ).toFixed( 0 ) }/100%`
221
+ )
222
+ .join( '\n' );
223
+ };
224
+
225
+ const { workDirectoryPath } = config;
226
+ const { development: dev, tests: test } = config.env;
227
+
228
+ let pluginDir = path.resolve(workDirectoryPath, 'plugins');
229
+ let themeDir = path.resolve(workDirectoryPath, 'themes');
230
+
231
+ let sources = dowloadPlugins(pluginDir);
232
+
233
+ // Add Divi Theme and plugin to sources.
234
+ if( (undefined !== dev.config.ET_USERNAME &&
235
+ undefined !== dev.config.ET_API_KEY) ||
236
+ (undefined !== test.config.ET_USERNAME &&
237
+ undefined !== test.config.ET_API_KEY)
238
+ ){
239
+ let url = 'https://www.elegantthemes.com/api/api_downloads.php';
240
+ let user = undefined !== dev.config.ET_USERNAME ? dev.config.ET_USERNAME : test.config.ET_USERNAME;
241
+ let key = undefined !== dev.config.ET_API_KEY ? dev.config.ET_API_KEY : test.config.ET_API_KEY;
242
+
243
+ // Add Divi sources.
244
+ sources = sources.concat( [
245
+ {
246
+ basename: 'Divi',
247
+ url: `${url}?api_update=1&theme=Divi&api_key=${key}&username=${user}`,
248
+ path: path.join(themeDir, 'Divi'),
249
+ type: 'zip'
250
+ },
251
+ {
252
+ basename: 'Divi Plugin',
253
+ url: `${url}?api_update=1&theme=divi-builder&api_key=${key}&username=${user}`,
254
+ path: path.join(pluginDir, 'divi-builder'),
255
+ type: 'zip'
256
+ }
257
+ ]);
258
+ }
259
+
260
+ // Ensure plugin/theme directory exists for downloading resources.
261
+ fs.ensureDir(themeDir);
262
+ fs.ensureDir(pluginDir);
263
+
264
+ await Promise.all(
265
+ sources.map( ( source ) =>
266
+ downloadSource( source, {
267
+ onProgress: getProgressSetter( source.basename ),
268
+ spinner,
269
+ } )
270
+ )
271
+ );
272
+
273
+ };