@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.
- package/commands/env/start.js +67 -68
- package/commands/gen-scripts.js +66 -0
- package/commands/index.js +12 -9
- package/commands/sites/convert-site.js +508 -152
- package/commands/sites/create-site.js +25 -3
- package/commands/sites/prompts.js +34 -15
- package/commands/sites/validation.js +68 -0
- package/commands/sync/index.js +530 -239
- package/configs/prompts.js +64 -0
- package/configs/wp-env.js +114 -25
- package/lib/cli.js +84 -72
- package/lib/wordpress/api.js +22 -13
- package/lib/wordpress/caweb.js +6 -20
- package/lib/wordpress/download-sources.js +38 -16
- package/lib/wordpress/options.js +21 -18
- package/lib/wordpress/wordpress.js +105 -30
- package/package.json +12 -7
- package/commands/env/destroy.js +0 -49
- package/commands/env/stop.js +0 -35
- package/configs/docker-compose.js +0 -132
- /package/commands/{tasks → env}/shell.js +0 -0
- /package/commands/{tasks → env}/update-plugins.js +0 -0
package/commands/sync/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { confirm, input, password } from '@inquirer/prompts';
|
|
|
15
15
|
* Internal dependencies
|
|
16
16
|
*/
|
|
17
17
|
import {
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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) ) : {
|
|
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
|
|
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
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
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
|
-
|
|
299
|
-
pages = await getParentItems(
|
|
300
|
-
pages,
|
|
301
|
-
targetOptions,
|
|
302
|
-
'pages',
|
|
303
|
-
debug
|
|
304
|
-
)
|
|
305
|
-
}
|
|
322
|
+
}
|
|
306
323
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
'
|
|
434
|
-
'
|
|
435
|
-
'
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
478
|
-
|
|
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
|
-
//
|
|
489
|
-
if(
|
|
493
|
+
// Posts.
|
|
494
|
+
if( tax.includes('posts') ){
|
|
490
495
|
// get all pages/posts
|
|
491
496
|
spinner.text = `Collecting all posts from ${target.url}`;
|
|
492
|
-
|
|
497
|
+
posts = await getTaxonomies({
|
|
493
498
|
...targetOptions,
|
|
494
499
|
orderby: 'parent',
|
|
495
|
-
include:
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
510
|
-
|
|
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
|
-
|
|
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
|
|