@caweb/cli 1.11.1 → 1.12.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.
@@ -15,7 +15,7 @@ import { confirm, input, password } from '@inquirer/prompts';
15
15
  * Internal dependencies
16
16
  */
17
17
  import {
18
- appPath,
18
+ appPath,
19
19
  getTaxonomies,
20
20
  createTaxonomies
21
21
  } from '../../lib/index.js';
@@ -95,7 +95,7 @@ async function getInstanceInfo( config, instance = 'target'){
95
95
  config = config ?? {};
96
96
 
97
97
  // add the target to sync list
98
- config.sync[nickname] = target;
98
+ config['sync'][nickname] = target;
99
99
 
100
100
  fs.writeFileSync(
101
101
  configFile,
@@ -107,6 +107,53 @@ async function getInstanceInfo( config, instance = 'target'){
107
107
  }
108
108
  }
109
109
 
110
+ /**
111
+ *
112
+ * @param {*} navJson
113
+ * @param {*} destUrl
114
+ * @returns {Array}
115
+ */
116
+ function getStaticNavItems(navJson, destUrl){
117
+ let navItems = [];
118
+
119
+ // iterate over the nav items
120
+ for( let item of navJson ){
121
+ let i = {
122
+ title: item.label,
123
+ url: item.url
124
+ }
125
+
126
+ if( item.description ){
127
+ i.description = item.description;
128
+ }
129
+
130
+ // if the item url is not the same as the destination url
131
+ // and is not relative, then the item is an external link
132
+ // if( item.url !== destUrl && ! item.url.startsWith('/') ){
133
+ // navItems.push(i)
134
+ // }else
135
+ if( item.url.startsWith('/') ){
136
+ // we remove the forward slash and the file extension if applicable
137
+ i.slug = item.url.replace(/^\//, '').replace(/\.[^/.]+$/, '');
138
+ i.url = `${destUrl}/${i.slug}`;
139
+ i.type = 'post_type';
140
+ i.object = 'page';
141
+ }
142
+
143
+ // if there is a submenu
144
+ if( item.sub && item.sub.length ){
145
+ i.sub = getStaticNavItems(item.sub, destUrl);
146
+ }
147
+
148
+
149
+ navItems.push(i)
150
+
151
+ }
152
+
153
+ return navItems;
154
+
155
+ }
156
+
110
157
  /**
111
158
  * Sync Environments.
112
159
  *
@@ -121,9 +168,10 @@ async function getInstanceInfo( config, instance = 'target'){
121
168
  * @param {Array} options.include Include specific IDs only.
122
169
  */
123
170
  export default async function sync({
124
- spinner,
125
- debug,
126
- target,
171
+ spinner,
172
+ debug,
173
+ config,
174
+ target,
127
175
  dest,
128
176
  interactive,
129
177
  tax,
@@ -137,11 +185,10 @@ export default async function sync({
137
185
  const {workDirectoryPath} = await loadConfig(path.resolve('.'));
138
186
 
139
187
  // read caweb configuration file.
140
- let serviceConfig = fs.existsSync(configFile) ? JSON.parse( fs.readFileSync(configFile) ) : { "sync": {} };
188
+ let serviceConfig = fs.existsSync(configFile) ? JSON.parse( fs.readFileSync(configFile) ) : { sync: {} };
141
189
 
142
- process.env.WP_CLI_CONFIG_PATH = path.join(workDirectoryPath, 'config.yml');
190
+ // process.env.WP_CLI_CONFIG_PATH = path.join(workDirectoryPath, 'config.yml');
143
191
 
144
- // get target and dest instance data
145
192
  target = serviceConfig.sync[target];
146
193
  dest = serviceConfig.sync[dest];
147
194
 
@@ -158,7 +205,6 @@ export default async function sync({
158
205
  }
159
206
  }
160
207
 
161
-
162
208
  // if no dest was specified or no dest saved.
163
209
  if( ! dest ){
164
210
  spinner.stop()
@@ -179,7 +225,7 @@ export default async function sync({
179
225
  ! target || ! target.url || ! target.user || ! target.pwd ||
180
226
  ! dest || ! dest.url || ! dest.user || ! dest.pwd
181
227
  ){
182
- spinner.fail(`caweb.json is not configured properly for ${target} and ${dest}.`);
228
+ spinner.fail(`caweb.json is not configured properly.`);
183
229
  process.exit(1)
184
230
  }
185
231
 
@@ -209,7 +255,7 @@ export default async function sync({
209
255
  * 5) We only collect menus if the taxonomy is undefined or it's set to menus.
210
256
  * - We also collect menu items if menus are collected.
211
257
  */
212
- let settings = [];
258
+ let settings = [{}];
213
259
  let mediaLibrary = [];
214
260
  let media = [];
215
261
  let pages = [];
@@ -242,272 +288,415 @@ export default async function sync({
242
288
 
243
289
  }
244
290
 
245
- // Media Library.
246
- if( tax.includes('media', 'pages', 'posts') ){
247
- spinner.text = `Collecting Media Library ${target.url}`;
248
- mediaLibrary = await getTaxonomies({
249
- ...targetOptions,
250
- fields: [
251
- 'id',
252
- 'source_url',
253
- 'title',
254
- 'caption',
255
- 'alt_text',
256
- 'date',
257
- 'mime_type',
258
- 'post',
259
- 'media_details'
260
- ],
261
- include: mediaIds && mediaIds.length ? mediaIds.join(',') : null
262
- },
263
- 'media',
264
- debug
265
- );
266
- }
291
+ // if the request is going from static,
292
+ // we create a taxonomies object based off of the static data.
293
+ if( 'static' === target.url ){
294
+ let mediaPath = serviceConfig.media;
295
+ let cawebSettings = [];
296
+
297
+ // iterate over pages and create a pages object.
298
+ for( let page of serviceConfig.pages ){
299
+ let content = page.shortcodeContent.replace('\"', '"');
300
+
301
+ pages.push({
302
+ title: page.title,
303
+ status: 'publish',
304
+ slug: page.slug,
305
+ content,
306
+ meta: {
307
+ _et_pb_use_builder: 'on', // this turns on the Divi Builder
308
+ }
309
+ })
310
+ }
267
311
 
268
- // Site Settings.
269
- if( tax.includes('settings') ){
270
- spinner.text = `Collecting Site Settings from ${target.url}`;
271
- settings = await getTaxonomies({
272
- ...targetOptions,
273
- fields: [
274
- 'title',
275
- 'description',
276
- 'show_on_front',
277
- 'page_on_front',
278
- 'posts_per_page'
279
- ],
280
- }, 'settings', debug )
281
-
282
- }
312
+ // Header Nav
313
+ if( serviceConfig?.site?.header?.nav ){
314
+ menus.push({
315
+ name: 'Header Menu',
316
+ slug: 'header-menu',
317
+ locations: 'header-menu',
318
+ });
283
319
 
284
- // Pages.
285
- if( tax.includes('pages') ){
286
- // get all pages/posts
287
- spinner.text = `Collecting pages from ${target.url}`;
288
- pages = await getTaxonomies({
289
- ...targetOptions,
290
- orderby: 'parent',
291
- embed: true,
292
- include: pageIds && pageIds.length ? pageIds.join(',') : null
293
- },
294
- 'pages',
295
- debug
296
- );
320
+ menuNavItems['header'] = getStaticNavItems(serviceConfig.site.header.nav, destOptions.url);
297
321
 
298
- // pages can be nested so we have to collect any parent items.
299
- pages = await getParentItems(
300
- pages,
301
- targetOptions,
302
- 'pages',
303
- debug
304
- )
305
- }
322
+ }
306
323
 
307
- // Posts.
308
- if( tax.includes('posts') ){
309
- // get all pages/posts
310
- spinner.text = `Collecting all posts from ${target.url}`;
311
- posts = await getTaxonomies({
312
- ...targetOptions,
313
- orderby: 'parent',
314
- include: postIds && postIds.length ? postIds.join(',') : null
315
- },
316
- 'posts',
317
- debug
318
- );
324
+ // Footer Nav
325
+ if( serviceConfig?.site?.footer?.nav ){
326
+ menus.push({
327
+ name: 'Footer Menu',
328
+ slug: 'footer-menu',
329
+ locations: 'footer-menu',
330
+ });
319
331
 
320
- // posts can be nested so we have to collect any parent items.
321
- posts = await getParentItems(
322
- posts,
323
- targetOptions,
324
- 'posts',
325
- debug
326
- )
327
- }
332
+ menuNavItems['footer'] = getStaticNavItems(serviceConfig.site.footer.nav, destOptions.url);
328
333
 
329
- /**
330
- * Media Library Handling
331
- *
332
- * We iterate thru the media library to identify which media should be synced.
333
- * 1) attached media items by default are only possible on pages.
334
- * 2) featured media by default are only possible on posts.
335
- * 3) save any linked media in the content of pages and posts.
336
- */
337
- for( let m of mediaLibrary ){
338
- // remove any new line characters.
339
- m.source_url = m.source_url.replace('\n','');
334
+ }
340
335
 
341
- /**
342
- * We collect the media if:
343
- * - media is attached
344
- * - id was explicitly requested
345
- * - tax includes media and media ids is blank
346
- */
347
- if(
348
- m.post ||
349
- ( mediaIds && mediaIds.includes( m.id.toString() ) ) ||
350
- ( tax.includes('media') && undefined === mediaIds )
351
- ){
352
- media.push( m );
353
-
354
- // we don't have to check any further.
355
- continue;
336
+ let currentDate = new Date();
337
+ let uploadDir = path.join(
338
+ 'wp-content', 'uploads',
339
+ currentDate.getFullYear().toString(), (currentDate.getMonth() + 1).toString().padStart(2, '0')
340
+ );
341
+
342
+ // if the media path exists
343
+ if( fs.existsSync(mediaPath) ){
344
+
345
+ // we read all the files in the media directory and create a media object.
346
+ fs.readdirSync(mediaPath, {recursive: true}).forEach( (file) => {
347
+
348
+ // update page content file references
349
+ pages.forEach((page) => {
350
+ // if the page content includes the file, we replace it the WordPress site upload dir.
351
+ if( page.content && page.content.includes(file) ){
352
+ // unescape slashes added from path.join.
353
+ page.content = page.content.replace(
354
+ new RegExp(`"[\\S]*${file}`, 'g'),
355
+ `"${destOptions.url}/` + path.join(uploadDir, file).replace(/\\/g, '/')
356
+ );
357
+
358
+ }
359
+ })
360
+
361
+ // if the file is a file, we read it and create a media object.
362
+ let filePath = path.join(mediaPath, file);
363
+
364
+ if( fs.statSync(filePath).isFile() ){
365
+ // read the file and create a media object.
366
+ let mediaBlob = fs.readFileSync(filePath);
367
+
368
+ // and new media object
369
+ media.push({
370
+ source_url: filePath,
371
+ title: path.basename(filePath).replace(/\.[^/.]+$/, ""),
372
+ alt_text: path.basename(filePath).replace(/\.[^/.]+$/, ""),
373
+ media_details:{},
374
+ date: currentDate.toISOString(),
375
+ data: new Blob([mediaBlob])
376
+ });
377
+ }
378
+ })
379
+
380
+ }
381
+
382
+ // Favicon is one level up from the media path.
383
+ let icoFile = path.join(mediaPath, '..', 'favicon.ico');
384
+
385
+ if( fs.existsSync(icoFile) && fs.statSync(icoFile).isFile() ){
386
+ // read the file and create a media object.
387
+ let icoBlob = fs.readFileSync(icoFile);
388
+
389
+ // and new media object
390
+ media.push({
391
+ source_url: icoFile,
392
+ title: 'favicon.ico',
393
+ alt_text: 'Fav Icon',
394
+ media_details:{},
395
+ date: currentDate.toISOString(),
396
+ data: new Blob([icoBlob])
397
+ });
356
398
  }
357
399
 
358
- for( let p of [].concat( pages, posts ) ){
359
- /**
360
- * We collect the media if:
361
- * - media is featured image
362
- * - media is the src attribute in the content
363
- */
364
- if( p.featured_media === m.id ||
365
- p.content.rendered.match( new RegExp(`src=”(${m.source_url}.*)”`, 'g') )){
366
- media.push( m );
367
- }
400
+ // Default Organization Logo.
401
+ // since all files in the media path are copied to the uploads directory,
402
+ // we only do the default one if the user hasn't added their own logo.
403
+ // the default logo is usually stored one level up from the media path.
404
+ // and in the caweb/template/media/logo.png
405
+ if( ! fs.existsSync(path.join(mediaPath, 'logo.png')) ){
406
+ let logoFile = path.join(mediaPath, '..', 'caweb', 'template', 'media', 'logo.png');
407
+
408
+ // read the file and create a media object.
409
+ let logoBlob = fs.readFileSync(logoFile);
410
+
411
+ // and new media object
412
+ media.push({
413
+ source_url: logoFile,
414
+ title: path.basename(logoFile),
415
+ alt_text: 'Organization Logo',
416
+ media_details:{},
417
+ date: currentDate.toISOString(),
418
+ data: new Blob([logoBlob])
419
+ });
420
+
368
421
  }
369
- }
370
422
 
371
- // filter any duplicate media.
372
- media = media.filter((m, index, self) => { return index === self.findIndex((t) => { return t.id === m.id; })} );
373
- let i = 0;
374
-
375
- // before we can upload media files we have to generate the media blob data.
376
- for( let m of media ){
377
- if( debug ){
378
- i++;
379
- spinner.info(`Media ID ${m.id} Collected: ${i}/${media.length}`)
423
+ // Settings
424
+ settings[0] = {
425
+ title: serviceConfig?.site?.title,
380
426
  }
381
-
382
- const mediaBlob = await axios.request(
383
- {
384
- ...targetOptions,
385
- url: m.source_url,
386
- responseType: 'arraybuffer'
387
- }
388
- )
389
- .then( (img) => { return new Blob([img.data]) })
390
- .catch( (error) => {
391
- errors.push(`${m.source_url} could not be downloaded.`)
392
- return false;
393
- });
394
-
395
- if( mediaBlob ){
396
- m.data = mediaBlob;
427
+
428
+ }else{
429
+
430
+ // Media Library.
431
+ if( tax.includes('media', 'pages', 'posts') ){
432
+ spinner.text = `Collecting Media Library ${target.url}`;
433
+ mediaLibrary = await getTaxonomies({
434
+ ...targetOptions,
435
+ fields: [
436
+ 'id',
437
+ 'source_url',
438
+ 'title',
439
+ 'caption',
440
+ 'alt_text',
441
+ 'date',
442
+ 'mime_type',
443
+ 'post',
444
+ 'media_details'
445
+ ],
446
+ include: mediaIds && mediaIds.length ? mediaIds.join(',') : null
447
+ },
448
+ 'media',
449
+ debug
450
+ );
397
451
  }
398
- }
399
-
400
- // this has to be done after we have the media data.
401
- // Lets replace the url references in the content of the pages and posts.
402
- for( let p of [].concat( pages, posts ) ){
403
- p.content.rendered = p.content.rendered.replace( new RegExp(target.url, 'g'), dest.url );
404
- }
405
452
 
406
- // Menu and Nav Items.
407
- if( tax.includes('menus')){
408
- spinner.text = `Collecting assigned navigation menus from ${target.url}`;
409
- // get all menus and navigation links
410
- menus = await getTaxonomies({
411
- ...targetOptions,
412
- fields: [
413
- 'id',
414
- 'description',
415
- 'name',
416
- 'slug',
417
- 'meta',
418
- 'locations'
419
- ],
420
- include: menuIds && menuIds.length ? menuIds.join(',') : null
421
- }, 'menus', debug);
422
-
423
- menuNavItems = await getTaxonomies(
424
- {
453
+ // Site Settings.
454
+ if( tax.includes('settings') ){
455
+ spinner.text = `Collecting Site Settings from ${target.url}`;
456
+ settings = await getTaxonomies({
425
457
  ...targetOptions,
426
458
  fields: [
427
- 'id',
428
459
  'title',
429
- 'url',
430
- 'status',
431
- 'attr_title',
432
460
  'description',
433
- 'type',
434
- 'type_label',
435
- 'object',
436
- 'object_id',
437
- 'parent',
438
- 'menu_order',
439
- 'target',
440
- 'classes',
441
- 'xfn',
442
- 'meta',
443
- 'menus'
461
+ 'show_on_front',
462
+ 'page_on_front',
463
+ 'posts_per_page'
444
464
  ],
445
- menus: menus.map((menu) => { return menu.id; })
446
- },
447
- 'menu-items',
448
- debug
449
- )
450
-
451
- let missingPages = [];
452
- let missingPosts = [];
453
- // we iterate over menu items
454
- menuNavItems.forEach(item => {
455
- // if the item is a page and it wasn't previously collected
456
- if( 'page' === item.object && ! pages.map(p => p.id ).includes(item.object_id) ){
457
- missingPages.push(item.object_id)
458
- // if the item is a post and it wasn't previously collected
459
- }else if( 'post' === item.object && ! posts.map(p => p.id ).includes(item.object_id) ){
460
- missingPosts.push(item.object_id)
461
- }
462
- })
463
-
464
- // if navigation pages weren't previously collected they must be collected.
465
- if( missingPages.length ){
466
- missingPages = await getTaxonomies({
467
- ...targetOptions,
468
- orderby: 'parent',
469
- embed: true,
470
- include: missingPages.join(',')
465
+ }, 'settings', debug )
466
+
467
+ }
468
+
469
+ // Pages.
470
+ if( tax.includes('pages') ){
471
+ // get all pages/posts
472
+ spinner.text = `Collecting pages from ${target.url}`;
473
+ pages = await getTaxonomies({
474
+ ...targetOptions,
475
+ orderby: 'parent',
476
+ embed: true,
477
+ include: pageIds && pageIds.length ? pageIds.join(',') : null
471
478
  },
472
479
  'pages',
473
480
  debug
474
481
  );
475
482
 
476
483
  // pages can be nested so we have to collect any parent items.
477
- missingPages = await getParentItems(
478
- missingPages,
484
+ pages = await getParentItems(
485
+ pages,
479
486
  targetOptions,
480
487
  'pages',
481
488
  debug
482
- );
489
+ )
483
490
 
484
- // add the missing pages to the pages array
485
- pages = pages.concat(missingPages)
486
491
  }
487
492
 
488
- // if navigation posts weren't previously collected they must be collected.
489
- if( missingPosts.length ){
493
+ // Posts.
494
+ if( tax.includes('posts') ){
490
495
  // get all pages/posts
491
496
  spinner.text = `Collecting all posts from ${target.url}`;
492
- missingPosts = await getTaxonomies({
497
+ posts = await getTaxonomies({
493
498
  ...targetOptions,
494
499
  orderby: 'parent',
495
- include: missingPosts.join(',')
500
+ include: postIds && postIds.length ? postIds.join(',') : null
496
501
  },
497
- 'posts',
502
+ 'posts',
498
503
  debug
499
504
  );
500
505
 
501
506
  // posts can be nested so we have to collect any parent items.
502
- missingPosts = await getParentItems(
503
- missingPosts,
504
- targetOptions,
505
- 'posts',
506
- debug
507
+ posts = await getParentItems(
508
+ posts,
509
+ targetOptions,
510
+ 'posts',
511
+ debug
512
+ )
513
+ }
514
+
515
+ /**
516
+ * Media Library Handling
517
+ *
518
+ * We iterate thru the media library to identify which media should be synced.
519
+ * 1) attached media items by default are only possible on pages.
520
+ * 2) featured media by default are only possible on posts.
521
+ * 3) save any linked media in the content of pages and posts.
522
+ */
523
+ for( let m of mediaLibrary ){
524
+ // remove any new line characters.
525
+ m.source_url = m.source_url.replace('\n','');
526
+
527
+ /**
528
+ * We collect the media if:
529
+ * - media is attached
530
+ * - id was explicitly requested
531
+ * - tax includes media and media ids is blank
532
+ */
533
+ if(
534
+ m.post ||
535
+ ( mediaIds && mediaIds.includes( m.id.toString() ) ) ||
536
+ ( tax.includes('media') && undefined === mediaIds )
537
+ ){
538
+ media.push( m );
539
+
540
+ // we don't have to check any further.
541
+ continue;
542
+ }
543
+
544
+ for( let p of [].concat( pages, posts ) ){
545
+ /**
546
+ * We collect the media if:
547
+ * - media is featured image
548
+ * - media is the src attribute in the content
549
+ */
550
+ if( p.featured_media === m.id ||
551
+ p.content.rendered.match( new RegExp(`src=”(${m.source_url}.*)”`, 'g') )){
552
+ media.push( m );
553
+ }
554
+ }
555
+ }
556
+
557
+ // filter any duplicate media.
558
+ media = media.filter((m, index, self) => { return index === self.findIndex((t) => { return t.id === m.id; })} );
559
+ let i = 0;
560
+
561
+ // before we can upload media files we have to generate the media blob data.
562
+ for( let m of media ){
563
+ if( debug ){
564
+ i++;
565
+ spinner.info(`Media ID ${m.id} Collected: ${i}/${media.length}`)
566
+ }
567
+
568
+ const mediaBlob = await axios.request(
569
+ {
570
+ ...targetOptions,
571
+ url: m.source_url,
572
+ responseType: 'arraybuffer'
573
+ }
507
574
  )
575
+ .then( (img) => { return new Blob([img.data]) })
576
+ .catch( (error) => {
577
+ errors.push(`${m.source_url} could not be downloaded.`)
578
+ return false;
579
+ });
508
580
 
509
- // add the missing posts to the posts array
510
- posts = posts.concat(missingPages);
581
+ if( mediaBlob ){
582
+ m.data = mediaBlob;
583
+ }
584
+ }
585
+
586
+ // this has to be done after we have the media data.
587
+ // Lets replace the url references in the content of the pages and posts.
588
+ for( let p of [].concat( pages, posts ) ){
589
+ if( p.content.rendered ){
590
+ p.content.rendered = p.content.rendered.replace( new RegExp(target.url, 'g'), dest.url );
591
+ }
592
+ }
593
+
594
+ // Menu and Nav Items.
595
+ if( tax.includes('menus')){
596
+ spinner.text = `Collecting assigned navigation menus from ${target.url}`;
597
+ // get all menus and navigation links
598
+ menus = await getTaxonomies({
599
+ ...targetOptions,
600
+ fields: [
601
+ 'id',
602
+ 'description',
603
+ 'name',
604
+ 'slug',
605
+ 'meta',
606
+ 'locations'
607
+ ],
608
+ include: menuIds && menuIds.length ? menuIds.join(',') : null
609
+ }, 'menus', debug);
610
+
611
+ menuNavItems = await getTaxonomies(
612
+ {
613
+ ...targetOptions,
614
+ fields: [
615
+ 'id',
616
+ 'title',
617
+ 'url',
618
+ 'status',
619
+ 'attr_title',
620
+ 'description',
621
+ 'type',
622
+ 'type_label',
623
+ 'object',
624
+ 'object_id',
625
+ 'parent',
626
+ 'menu_order',
627
+ 'target',
628
+ 'classes',
629
+ 'xfn',
630
+ 'meta',
631
+ 'menus'
632
+ ],
633
+ menus: menus.map((menu) => { return menu.id; })
634
+ },
635
+ 'menu-items',
636
+ debug
637
+ )
638
+
639
+ let missingPages = [];
640
+ let missingPosts = [];
641
+ // we iterate over menu items
642
+ menuNavItems.forEach(item => {
643
+ // if the item is a page and it wasn't previously collected
644
+ if( 'page' === item.object && ! pages.map(p => p.id ).includes(item.object_id) ){
645
+ missingPages.push(item.object_id)
646
+ // if the item is a post and it wasn't previously collected
647
+ }else if( 'post' === item.object && ! posts.map(p => p.id ).includes(item.object_id) ){
648
+ missingPosts.push(item.object_id)
649
+ }
650
+ })
651
+
652
+ // if navigation pages weren't previously collected they must be collected.
653
+ if( missingPages.length ){
654
+ missingPages = await getTaxonomies({
655
+ ...targetOptions,
656
+ orderby: 'parent',
657
+ embed: true,
658
+ include: missingPages.join(',')
659
+ },
660
+ 'pages',
661
+ debug
662
+ );
663
+
664
+ // pages can be nested so we have to collect any parent items.
665
+ missingPages = await getParentItems(
666
+ missingPages,
667
+ targetOptions,
668
+ 'pages',
669
+ debug
670
+ );
671
+
672
+ // add the missing pages to the pages array
673
+ pages = pages.concat(missingPages)
674
+ }
675
+
676
+ // if navigation posts weren't previously collected they must be collected.
677
+ if( missingPosts.length ){
678
+ // get all pages/posts
679
+ spinner.text = `Collecting all posts from ${target.url}`;
680
+ missingPosts = await getTaxonomies({
681
+ ...targetOptions,
682
+ orderby: 'parent',
683
+ include: missingPosts.join(',')
684
+ },
685
+ 'posts',
686
+ debug
687
+ );
688
+
689
+ // posts can be nested so we have to collect any parent items.
690
+ missingPosts = await getParentItems(
691
+ missingPosts,
692
+ targetOptions,
693
+ 'posts',
694
+ debug
695
+ )
696
+
697
+ // add the missing posts to the posts array
698
+ posts = posts.concat(missingPages);
699
+ }
511
700
  }
512
701
  }
513
702
 
@@ -534,11 +723,12 @@ export default async function sync({
534
723
  }
535
724
 
536
725
  // Pages.
726
+ let createdPages = [];
537
727
  if( pages ){
538
728
  spinner.text = `Creating all pages to ${dest.url}`;
539
- await createTaxonomies( pages, destOptions, 'pages', spinner );
729
+ createdPages = await createTaxonomies( pages, destOptions, 'pages', spinner );
540
730
  }
541
-
731
+
542
732
  // Posts.
543
733
  if( posts ){
544
734
  spinner.text = `Creating all posts to ${dest.url}`;
@@ -548,14 +738,115 @@ export default async function sync({
548
738
  // Menus and Navigation Links.
549
739
  if( menus ){
550
740
  spinner.text = `Reconstructing navigation menus to ${dest.url}`;
551
- await createTaxonomies(menus, destOptions, 'menus', spinner);
552
- await createTaxonomies(menuNavItems, destOptions, 'menu-items', spinner);
741
+ let createdMenus = await createTaxonomies(menus, destOptions, 'menus', spinner);
742
+
743
+ // this variable is only used if the request is going from static
744
+ // we initialize it here outside of the if statement
745
+ // since we need to assign the ids of the created menu items to the sub menu items.
746
+ let subMenus = [];
747
+
748
+ // only if the request is going from static
749
+ if( 'static' === target.url ){
750
+ let headerMenu = createdMenus.find((menu) => { return menu.slug === 'header-menu'; }) || {};
751
+ let footerMenu = createdMenus.find((menu) => { return menu.slug === 'footer-menu'; }) || {};
752
+
753
+ /**
754
+ * Modify menu items.
755
+ *
756
+ * We iterate over the menu items and delete certain properties.
757
+ * We also assign the menu id to the menu items.
758
+ * We also assign the page id to the menu items if they have a slug.
759
+ *
760
+ */
761
+ Object.entries(menuNavItems).forEach(([key, value]) => {
762
+ let menuId = 'header' === key ? headerMenu?.id : footerMenu?.id;
763
+
764
+ // if the required menu is not created, we delete the menu item
765
+ if( ! menuId ){
766
+ delete menuNavItems[key];
767
+ return;
768
+ }
769
+
770
+ value.forEach((v, k) => {
771
+ // assign the newly created menu ids to the menu items
772
+ v.menus = menuId;
773
+
774
+ // if the item has a slug we update the url and remove the slug property
775
+ if( v.slug ){
776
+ // find the page id
777
+ let pageId = createdPages.find((p) => { return p.slug === v.slug; })?.id;
778
+
779
+ v.object_id = pageId;
780
+
781
+ delete v.slug;
782
+ }
783
+
784
+ // if the item has sub items we have to assign the sub items to the parent item
785
+ if( v.sub ){
786
+ v.sub.forEach((subItem, subIndex) => {
787
+ subItem.menus = menuId;
788
+
789
+ // if the item has a slug we update the url and remove the slug property
790
+ if( subItem.slug ){
791
+ // find the page id
792
+ let subPageId = createdPages.find((p) => { return p.slug === subItem.slug; })?.id;
793
+
794
+ subItem.object_id = subPageId;
795
+
796
+ delete subItem.slug;
797
+ }
798
+ })
799
+
800
+ subMenus[k] = v.sub;
801
+
802
+ delete v.sub;
803
+ }
804
+ })
805
+
806
+ // this ensures that the array contains only the menu items
807
+ // we add the value back to the menuNavItems
808
+ // delete the key from the menuNavItems
809
+ menuNavItems = [
810
+ ...menuNavItems,
811
+ ...value
812
+ ]
813
+
814
+ delete menuNavItems[key];
815
+ })
816
+ }
817
+
818
+ let createdMenuItems = await createTaxonomies(menuNavItems, destOptions, 'menu-items', spinner);
819
+
820
+ // if we have sub menus, we have to create them as well.
821
+ if( subMenus && subMenus.length ){
822
+ // we iterate over the sub menus and assign the parent id to the sub menu items
823
+ subMenus.forEach((sub, index) => {
824
+ sub.forEach((s) => {
825
+ s.parent = createdMenuItems[index].id;
826
+ s.menu_order = index;
827
+ })
828
+ })
829
+
830
+ // now we can create the sub menus
831
+ await createTaxonomies(subMenus.filter( i => i).flat(), destOptions, 'menu-items', spinner);
832
+ }
553
833
  }
554
834
 
555
835
 
556
836
  // Settings.
557
837
  if( settings ){
558
838
  spinner.text = `Updating site settings to ${dest.url}`;
839
+
840
+ // if going from static, we have to set the homepage and posts page.
841
+ if( 'static' === target.url ){
842
+ let homepageID = createdPages.find((p) => { return 'index' === p.slug; })?.id || 0;
843
+
844
+ if( homepageID ){
845
+ settings[0]['page_on_front'] = homepageID;
846
+ settings[0]['show_on_front'] = 'page';
847
+ }
848
+ }
849
+
559
850
  await createTaxonomies(settings, destOptions, 'settings', spinner);
560
851
  }
561
852