@caweb/cli 1.3.0 → 1.3.2
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/README.md +10 -5
- package/commands/env/start.js +19 -6
- package/commands/sync.js +173 -394
- package/commands/test.js +12 -58
- package/configs/webpack.config.js +1 -1
- package/lib/cli.js +4 -1
- package/lib/helpers.js +1 -1
- package/lib/index.js +26 -1
- package/lib/spinner.js +30 -1
- package/lib/wordpress/api.js +303 -0
- package/lib/wordpress/index.js +7 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ The following WordPress packages are used:
|
|
|
11
11
|
- <strong>For Debian-Based Linux distributions:</strong> <code>docker-compose</code> may need to be installed with: <code>sudo apt-get install docker-compose</code>.
|
|
12
12
|
- <strong>For Windows users:</strong> [WSL should be set to version 2 for Windows Docker Desktop compatibility](https://docs.docker.com/desktop/windows/wsl/).
|
|
13
13
|
- git is installed.
|
|
14
|
+
- php is installed.
|
|
14
15
|
|
|
15
16
|
## Additional Features
|
|
16
17
|
- Downloads and configures the [CAWeb Theme](https://github.com/CAWebPublishing/CAWeb)
|
|
@@ -20,13 +21,17 @@ The following WordPress packages are used:
|
|
|
20
21
|
- phpMyAdmin development site starts at http://localhost:8080
|
|
21
22
|
- phpMyAdmin test site started at http://localhost:9090
|
|
22
23
|
- Uses CAWebPublishing [External Project Template Configuration](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/packages-create-block-external-template/) when creating Gutenberg Blocks, see configurations [here](https://github.com/CAWebPublishing/cli/lib/template)
|
|
23
|
-
-
|
|
24
|
+
- Allows for syncing of WordPress instance via Rest API and CLI, requires ssh.
|
|
25
|
+
- Adds config.yml to both cli containers, alias are added for each ssh connection.
|
|
26
|
+
<b>Example config.yml file</b>
|
|
24
27
|
<pre>
|
|
25
|
-
path: /var/www/html
|
|
26
|
-
apache_modules:
|
|
27
|
-
|
|
28
|
+
path: /var/www/html
|
|
29
|
+
apache_modules:
|
|
30
|
+
- mod_rewrite
|
|
31
|
+
@staging:
|
|
32
|
+
ssh: wpcli@staging.wp-cli.org
|
|
28
33
|
</pre>
|
|
29
|
-
|
|
34
|
+
|
|
30
35
|
|
|
31
36
|
## Command Reference
|
|
32
37
|
### `caweb start`
|
package/commands/env/start.js
CHANGED
|
@@ -20,15 +20,17 @@ const CONFIG_CACHE_KEY = 'config_checksum';
|
|
|
20
20
|
/**
|
|
21
21
|
* Internal dependencies
|
|
22
22
|
*/
|
|
23
|
-
import { appPath } from '../../lib/helpers.js';
|
|
24
|
-
import wpEnvConfig from '../../configs/wp-env.js';
|
|
25
|
-
import dockerConfig from '../../configs/docker-compose.js';
|
|
26
|
-
|
|
27
23
|
import {
|
|
24
|
+
appPath,
|
|
25
|
+
projectPath,
|
|
28
26
|
configureCAWeb,
|
|
29
27
|
downloadSources,
|
|
30
|
-
configureWordPress
|
|
31
|
-
|
|
28
|
+
configureWordPress,
|
|
29
|
+
runCmd
|
|
30
|
+
} from '../../lib/index.js';
|
|
31
|
+
import wpEnvConfig from '../../configs/wp-env.js';
|
|
32
|
+
import dockerConfig from '../../configs/docker-compose.js';
|
|
33
|
+
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* Starts the development server.
|
|
@@ -145,6 +147,17 @@ export default async function start({
|
|
|
145
147
|
} ),
|
|
146
148
|
] );
|
|
147
149
|
|
|
150
|
+
// Create an Application Password for the user.
|
|
151
|
+
/*
|
|
152
|
+
const devAppPwd = await runCLICmd(
|
|
153
|
+
'wp',
|
|
154
|
+
[
|
|
155
|
+
`user application-password create 1 caweb`,
|
|
156
|
+
'--porcelain'
|
|
157
|
+
]
|
|
158
|
+
);
|
|
159
|
+
*/
|
|
160
|
+
|
|
148
161
|
}
|
|
149
162
|
|
|
150
163
|
// Start phpMyAdmin Service.
|
package/commands/sync.js
CHANGED
|
@@ -3,339 +3,21 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import fs from 'fs';
|
|
6
|
-
import axios from 'axios';
|
|
7
|
-
import terminalLink from 'terminal-link';
|
|
8
6
|
import loadConfig from '@wordpress/env/lib/config/load-config.js';
|
|
7
|
+
import axios from 'axios';
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Internal dependencies
|
|
12
11
|
*/
|
|
13
12
|
import {
|
|
14
13
|
appPath,
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
getTaxonomies,
|
|
15
|
+
createTaxonomies
|
|
17
16
|
} from '../lib/index.js';
|
|
18
17
|
|
|
19
|
-
const
|
|
20
|
-
const requestDelay = 1000; // we wait 1 second between requests
|
|
21
|
-
|
|
22
|
-
const sleep = (waitTimeInMs) => new Promise(resolve => setTimeout(resolve, waitTimeInMs));
|
|
23
|
-
|
|
24
|
-
function processData( data ){
|
|
25
|
-
/**
|
|
26
|
-
* If wpautop is enabled and data contains Divi shortcodes,
|
|
27
|
-
* WordPress will automatically add an opening/closing p tag that needs to be removed.
|
|
28
|
-
* @link https://developer.wordpress.org/reference/functions/wpautop/
|
|
29
|
-
*/
|
|
30
|
-
if( data.includes('et_pb_section') &&
|
|
31
|
-
data.startsWith('<p>') &&
|
|
32
|
-
data.endsWith('</p>\n')
|
|
33
|
-
){
|
|
34
|
-
data = data.substring(3, data.length - 5)
|
|
35
|
-
}
|
|
36
|
-
return data
|
|
37
|
-
.replace(/(”)|(″)|(“)/g, '') // replace double encoding
|
|
38
|
-
.replace(/(\s{2,})/g, ' ') // replace misc spacing // “
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function getTaxonomies( request, tax = 'pages', limit = 5000 ){
|
|
43
|
-
const fields = request.fields ? request.fields : [
|
|
44
|
-
'id',
|
|
45
|
-
'type',
|
|
46
|
-
'alt_text',
|
|
47
|
-
'caption',
|
|
48
|
-
'slug',
|
|
49
|
-
'title',
|
|
50
|
-
'source_url',
|
|
51
|
-
'mime_type',
|
|
52
|
-
'content',
|
|
53
|
-
'date',
|
|
54
|
-
'date_gmt',
|
|
55
|
-
'status',
|
|
56
|
-
'featured_media',
|
|
57
|
-
'comment_status',
|
|
58
|
-
'ping_status',
|
|
59
|
-
'template',
|
|
60
|
-
'format',
|
|
61
|
-
'categories',
|
|
62
|
-
'tags',
|
|
63
|
-
'meta'
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
let urlParams = [
|
|
67
|
-
'per_page=100',
|
|
68
|
-
`_fields=${ fields.join(',')}`,
|
|
69
|
-
'order=asc'
|
|
70
|
-
]
|
|
71
|
-
|
|
72
|
-
// if no request is added default to GET
|
|
73
|
-
if( ! request.method ){
|
|
74
|
-
request.method = 'GET'
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// if parent argument
|
|
78
|
-
if( request.parent ){
|
|
79
|
-
urlParams.push( `parent=` + ( Array === typeof request.parent ? request.parent.join(',') : request.parent ))
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// if include argument
|
|
83
|
-
if( request.include ){
|
|
84
|
-
urlParams.push( `include=` + ( Array === typeof request.include ? request.include.join(',') : request.include ))
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let collection = [];
|
|
88
|
-
|
|
89
|
-
for( let i = 1; i < limit / 100; i++ ){
|
|
90
|
-
// make requests for each page
|
|
91
|
-
urlParams.push(`page=${i}`)
|
|
92
|
-
|
|
93
|
-
let results = await axios.request(
|
|
94
|
-
{
|
|
95
|
-
...request,
|
|
96
|
-
url: `${request.url}${endpoint}/${tax}?` + urlParams.join('&')
|
|
97
|
-
}
|
|
98
|
-
).then(
|
|
99
|
-
(res) => {
|
|
100
|
-
return res.data
|
|
101
|
-
}).catch(
|
|
102
|
-
(error) => {return false}
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// if no more results stop making requests
|
|
107
|
-
if( ! results ){
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// add results to collection
|
|
112
|
-
collection = collection.concat( results );
|
|
113
|
-
|
|
114
|
-
// lets wait requestDelay between requests.
|
|
115
|
-
await sleep( requestDelay );
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return collection;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async function createTaxonomies( taxData, request, tax = 'pages', spinner ){
|
|
122
|
-
// if no request is added default to POST
|
|
123
|
-
if( ! request.method ){
|
|
124
|
-
request.method = 'POST'
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
let collection = [];
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
for( let obj of taxData ){
|
|
131
|
-
// endpoint url.
|
|
132
|
-
let url = `${request.url}${endpoint}/${tax}`;
|
|
133
|
-
let existingID = false;
|
|
134
|
-
|
|
135
|
-
// in order to maintain ID's, we have to use the cli, the REST API doesn't allow passing the id.
|
|
136
|
-
if( obj.id ){
|
|
137
|
-
// first we check if the ID exist
|
|
138
|
-
let idExists = await axios.request(
|
|
139
|
-
{
|
|
140
|
-
...request,
|
|
141
|
-
method: 'GET',
|
|
142
|
-
url: `${url}/${ obj.id }`
|
|
143
|
-
}
|
|
144
|
-
)
|
|
145
|
-
.then((res) => { return res.data.id })
|
|
146
|
-
.catch(error => {return false;})
|
|
147
|
-
|
|
148
|
-
// for menus if ID doesn't exist we also check for matching slug
|
|
149
|
-
// if a menu with a matching slug exists we return that id instead.
|
|
150
|
-
if( 'menus' === tax && ! idExists ){
|
|
151
|
-
idExists = await axios.request(
|
|
152
|
-
{
|
|
153
|
-
...request,
|
|
154
|
-
method: 'GET',
|
|
155
|
-
url: `${url}/?slug=${ obj.slug }`
|
|
156
|
-
}
|
|
157
|
-
)
|
|
158
|
-
.then((res) => { return res.data[0].id })
|
|
159
|
-
.catch(error => {return false;})
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* if the ID doesn't exist we save it so we can update via CLI later on.
|
|
165
|
-
* if it does exist update endpoint url so we update the existing item.
|
|
166
|
-
*/
|
|
167
|
-
if( ! idExists ){
|
|
168
|
-
existingID = obj.id;
|
|
169
|
-
}else{
|
|
170
|
-
url = `${url}/${ obj.id }`;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// id has to be deleted.
|
|
174
|
-
delete obj.id;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// process properties
|
|
178
|
-
if( obj.content ){
|
|
179
|
-
obj.content = processData(obj.content.rendered);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if( obj.title ){
|
|
183
|
-
obj.title = processData(obj.title.rendered);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if( obj.caption ){
|
|
187
|
-
obj.caption = processData(obj.caption.rendered);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// make WordPress REST API request.
|
|
191
|
-
let results = await axios.request({
|
|
192
|
-
...request,
|
|
193
|
-
data: 'media' === tax ? createMediaItem(obj) : obj,
|
|
194
|
-
url
|
|
195
|
-
})
|
|
196
|
-
.then( async (res) => { return res.data; }, axiosErrorHandler )
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* if the obj had an existing ID that didn't exist
|
|
201
|
-
*/
|
|
202
|
-
if( existingID ){
|
|
203
|
-
|
|
204
|
-
let post_tbl = `wp_posts`;
|
|
205
|
-
let post_meta_tbl = `wp_postmeta`;
|
|
206
|
-
let term_taxonomy_tbl = `wp_term_taxonomy`;
|
|
207
|
-
|
|
208
|
-
const cmd = [
|
|
209
|
-
path.join(projectPath, 'bin', 'wp-cli.phar'),
|
|
210
|
-
`--ssh=${request.ssh}`,
|
|
211
|
-
'--skip-themes',
|
|
212
|
-
'--skip-column-names',
|
|
213
|
-
`--url=${new URL(request.url).origin}`
|
|
214
|
-
];
|
|
215
|
-
|
|
216
|
-
// if taxonomy is page/post/media.
|
|
217
|
-
if( ['pages', 'posts', 'media'].includes(tax) ){
|
|
18
|
+
const errors = [];
|
|
218
19
|
|
|
219
20
|
|
|
220
|
-
/**
|
|
221
|
-
* Since the REST API doesn't detected trashed posts/pages.
|
|
222
|
-
* We have to do a drop before we can do an update.
|
|
223
|
-
* Since trashed posts/pages stay in the database
|
|
224
|
-
*/
|
|
225
|
-
// drop post
|
|
226
|
-
await runCmd(
|
|
227
|
-
'php',
|
|
228
|
-
[
|
|
229
|
-
...cmd,
|
|
230
|
-
`db query 'DELETE FROM ${post_tbl} WHERE ID=${existingID}'`,
|
|
231
|
-
]
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
// drop post meta.
|
|
235
|
-
await runCmd(
|
|
236
|
-
'php',
|
|
237
|
-
[
|
|
238
|
-
...cmd,
|
|
239
|
-
`db query 'DELETE FROM ${post_meta_tbl} WHERE post_id=${existingID}'`,
|
|
240
|
-
]
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
// now we can update the new ID with the existing ID.
|
|
244
|
-
await runCmd(
|
|
245
|
-
'php',
|
|
246
|
-
[
|
|
247
|
-
...cmd,
|
|
248
|
-
`db query 'UPDATE ${post_tbl} SET ID=${existingID} WHERE ID=${results.id}'`,
|
|
249
|
-
]
|
|
250
|
-
)
|
|
251
|
-
// update post meta.
|
|
252
|
-
await runCmd(
|
|
253
|
-
'php',
|
|
254
|
-
[
|
|
255
|
-
...cmd,
|
|
256
|
-
`db query 'UPDATE ${post_meta_tbl} SET post_id=${existingID} WHERE post_id=${results.id}'`,
|
|
257
|
-
]
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
// if taxonomy is menu.
|
|
261
|
-
}else if( 'menus' === tax ){
|
|
262
|
-
// update the term taxonomy table.
|
|
263
|
-
await runCmd(
|
|
264
|
-
'php',
|
|
265
|
-
[
|
|
266
|
-
...cmd,
|
|
267
|
-
`db query 'UPDATE ${term_taxonomy_tbl} SET term_taxonomy_id=${existingID},term_id=${existingID} WHERE term_id=${results.id}'`,
|
|
268
|
-
]
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// update the API results ID, back to the existing ID from earlier.
|
|
274
|
-
results.id = existingID;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// add results to collection
|
|
278
|
-
collection = collection.concat( results );
|
|
279
|
-
|
|
280
|
-
// We wait requestDelay seconds between requests.
|
|
281
|
-
await sleep( requestDelay );
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function createMediaItem( media ){
|
|
289
|
-
|
|
290
|
-
let fd = new FormData();
|
|
291
|
-
let featureMediaFile = new File(
|
|
292
|
-
[
|
|
293
|
-
media.data
|
|
294
|
-
],
|
|
295
|
-
media.source_url.split('/').pop(),
|
|
296
|
-
{
|
|
297
|
-
type: media.mime_type
|
|
298
|
-
}
|
|
299
|
-
);
|
|
300
|
-
fd.append('file', featureMediaFile );
|
|
301
|
-
fd.append('title', media.title);
|
|
302
|
-
fd.append('caption', media.caption);
|
|
303
|
-
fd.append('alt_text', media.alt_text);
|
|
304
|
-
fd.append('date', media.date);
|
|
305
|
-
|
|
306
|
-
return fd;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function axiosErrorHandler( error ){
|
|
310
|
-
|
|
311
|
-
if (error.response) {
|
|
312
|
-
// The request was made and the server responded with a status code
|
|
313
|
-
// that falls out of the range of 2xx
|
|
314
|
-
let { data, status } = error.response;
|
|
315
|
-
|
|
316
|
-
switch( status ){
|
|
317
|
-
case 401: // Invalid Credentials
|
|
318
|
-
data.message += `\nPlease check your ${terminalLink('Application Password', 'https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/')}`;
|
|
319
|
-
case 404: // Not Found
|
|
320
|
-
//spinner.fail( `${data.message}` )
|
|
321
|
-
//console.log('Error', `${data.message}`);
|
|
322
|
-
//console.log('Error', `${data}`);
|
|
323
|
-
//process.exit(1)
|
|
324
|
-
default:
|
|
325
|
-
console.log('Error', error.response);
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
} else if (error.request) {
|
|
329
|
-
// The request was made but no response was received
|
|
330
|
-
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
|
331
|
-
// http.ClientRequest in node.js
|
|
332
|
-
console.log(error.request);
|
|
333
|
-
} else {
|
|
334
|
-
// Something happened in setting up the request that triggered an Error
|
|
335
|
-
console.log('Error', error.message);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
21
|
/**
|
|
340
22
|
* Sync Environments.
|
|
341
23
|
*
|
|
@@ -344,12 +26,14 @@ function axiosErrorHandler( error ){
|
|
|
344
26
|
* @param {boolean} options.from Remote Site URL with current changes.
|
|
345
27
|
* @param {boolean} options.to Destination Site URL that should be synced.
|
|
346
28
|
* @param {boolean} options.debug True if debug mode is enabled.
|
|
29
|
+
* @param {Array} options.tax Taxonomy that should be synced.
|
|
347
30
|
*/
|
|
348
31
|
export default async function sync({
|
|
349
32
|
spinner,
|
|
350
33
|
debug,
|
|
351
34
|
from,
|
|
352
|
-
to
|
|
35
|
+
to,
|
|
36
|
+
tax
|
|
353
37
|
} ) {
|
|
354
38
|
|
|
355
39
|
const localFile = path.join(appPath, 'caweb.json');
|
|
@@ -409,90 +93,185 @@ export default async function sync({
|
|
|
409
93
|
}
|
|
410
94
|
}
|
|
411
95
|
|
|
96
|
+
let pages = [];
|
|
97
|
+
let posts = [];
|
|
98
|
+
let attachedMedia = [];
|
|
99
|
+
let featuredMedia = [];
|
|
100
|
+
let menus = [];
|
|
101
|
+
let menuNavItems = [];
|
|
102
|
+
|
|
103
|
+
// get all settings
|
|
104
|
+
// site settings are always synced
|
|
105
|
+
spinner.text = `Getting Site Settings from ${from.url}`;
|
|
106
|
+
let settings = await getTaxonomies({
|
|
107
|
+
...fromOptions,
|
|
108
|
+
fields: [
|
|
109
|
+
'show_on_front',
|
|
110
|
+
'page_on_front'
|
|
111
|
+
],
|
|
112
|
+
}, 'settings')
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* If taxonomy is undefined then we sync everything, otherwise we only sync what's being requested.
|
|
116
|
+
* First we collect everything we need from the base, then we send the information to the target.
|
|
117
|
+
* If media is requested then we need to download posts/pages to get any attached/featured media.
|
|
118
|
+
*/
|
|
119
|
+
// collect pages
|
|
120
|
+
if( undefined === tax || tax.includes('pages') || tax.includes('media')){
|
|
121
|
+
// get all pages/posts
|
|
122
|
+
spinner.text = `Gathering all pages from ${from.url}`;
|
|
123
|
+
pages = await getTaxonomies(fromOptions);
|
|
124
|
+
}
|
|
412
125
|
|
|
413
|
-
//
|
|
414
|
-
|
|
415
|
-
|
|
126
|
+
// collect posts
|
|
127
|
+
if( undefined === tax || tax.includes('posts')|| tax.includes('media')){
|
|
128
|
+
// get all pages/posts
|
|
129
|
+
spinner.text = `Gathering all posts from ${from.url}`;
|
|
130
|
+
posts = await getTaxonomies(fromOptions, 'posts');
|
|
131
|
+
}
|
|
416
132
|
|
|
417
|
-
//
|
|
418
|
-
|
|
133
|
+
// collect media
|
|
134
|
+
if( undefined === tax || tax.includes('media')){
|
|
135
|
+
|
|
136
|
+
spinner.text = `Collecting all attached/featured images from ${from.url}`;
|
|
137
|
+
/**
|
|
138
|
+
* Media Library Handling
|
|
139
|
+
* 1) attached media items by default are only possible on pages.
|
|
140
|
+
* 2) featured media by default are only possible on posts.
|
|
141
|
+
*/
|
|
142
|
+
const mediaFields = [
|
|
143
|
+
'id',
|
|
144
|
+
'source_url',
|
|
145
|
+
'title',
|
|
146
|
+
'caption',
|
|
147
|
+
'alt_text',
|
|
148
|
+
'date',
|
|
149
|
+
'mime_type'
|
|
150
|
+
];
|
|
151
|
+
attachedMedia = await getTaxonomies({
|
|
152
|
+
...fromOptions,
|
|
153
|
+
fields: mediaFields,
|
|
154
|
+
parent: pages.map((p) => { return p.id })
|
|
155
|
+
},
|
|
156
|
+
'media'
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
featuredMedia = await getTaxonomies({
|
|
160
|
+
...fromOptions,
|
|
161
|
+
fields: mediaFields,
|
|
162
|
+
include: posts.map((p) => { if(p.featured_media){return p.featured_media } })
|
|
163
|
+
},
|
|
164
|
+
'media'
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// before we can upload media files.
|
|
168
|
+
for( let mediaObj of [].concat( attachedMedia, featuredMedia ) ){
|
|
169
|
+
// generate a blob from the media object source_url.
|
|
170
|
+
const mediaBlob = await axios.request(
|
|
171
|
+
{
|
|
172
|
+
...fromOptions,
|
|
173
|
+
url: mediaObj.source_url,
|
|
174
|
+
responseType: 'arraybuffer'
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
.then( (img) => { return new Blob([img.data]) })
|
|
178
|
+
.catch( (error) => {
|
|
179
|
+
errors.push(`${mediaObj.source_url} could not be downloaded.`)
|
|
180
|
+
return false;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if( mediaBlob ){
|
|
184
|
+
mediaObj.data = mediaBlob;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
419
188
|
|
|
420
|
-
|
|
189
|
+
// collect menu and nav items.
|
|
190
|
+
if( undefined === tax || tax.includes('menus')){
|
|
191
|
+
spinner.text = `Collecting assigned navigation menus from ${from.url}`;
|
|
192
|
+
// get all menus and navigation links
|
|
193
|
+
menus = await getTaxonomies({
|
|
194
|
+
...fromOptions,
|
|
195
|
+
fields: [
|
|
196
|
+
'id',
|
|
197
|
+
'description',
|
|
198
|
+
'name',
|
|
199
|
+
'slug',
|
|
200
|
+
'meta',
|
|
201
|
+
'locations'
|
|
202
|
+
]
|
|
203
|
+
}, 'menus');
|
|
204
|
+
|
|
205
|
+
menuNavItems = await getTaxonomies(
|
|
206
|
+
{
|
|
207
|
+
...fromOptions,
|
|
208
|
+
fields: [
|
|
209
|
+
'id',
|
|
210
|
+
'title',
|
|
211
|
+
'url',
|
|
212
|
+
'status',
|
|
213
|
+
'attr_title',
|
|
214
|
+
'description',
|
|
215
|
+
'type',
|
|
216
|
+
'type_label',
|
|
217
|
+
'object',
|
|
218
|
+
'object_id',
|
|
219
|
+
'parent',
|
|
220
|
+
'menu_order',
|
|
221
|
+
'target',
|
|
222
|
+
'classes',
|
|
223
|
+
'xfn',
|
|
224
|
+
'meta',
|
|
225
|
+
'menus'
|
|
226
|
+
],
|
|
227
|
+
menus: menus.map((menu) => { return menu.id; })
|
|
228
|
+
},
|
|
229
|
+
'menu-items'
|
|
230
|
+
)
|
|
421
231
|
|
|
422
|
-
|
|
423
|
-
spinner.text = `Creating all pages to ${to.url}`;
|
|
424
|
-
await createTaxonomies( pages, toOptions, 'pages', spinner );
|
|
425
|
-
await createTaxonomies( posts, toOptions, 'posts', spinner );
|
|
232
|
+
}
|
|
426
233
|
|
|
427
234
|
/**
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
235
|
+
* Now we have all the data we can begin to create the taxonomies on the target.
|
|
236
|
+
*
|
|
237
|
+
* Creation order is important otherwise missing dependencies will cause errors to trigger.
|
|
238
|
+
* We create media first, then pages, then posts, then menus and navigation links.
|
|
431
239
|
*/
|
|
432
|
-
const mediaFields = [
|
|
433
|
-
'id',
|
|
434
|
-
'source_url',
|
|
435
|
-
'title',
|
|
436
|
-
'caption',
|
|
437
|
-
'alt_text',
|
|
438
|
-
'date',
|
|
439
|
-
'mime_type'
|
|
440
|
-
];
|
|
441
240
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
fields: mediaFields,
|
|
453
|
-
include: posts.map((p) => { if(p.featured_media){return p.featured_media } })
|
|
454
|
-
},
|
|
455
|
-
'media'
|
|
456
|
-
);
|
|
241
|
+
// create media attachments
|
|
242
|
+
if( undefined === tax || tax.includes('media')){
|
|
243
|
+
spinner.text = `Uploading media files to ${to.url}`;
|
|
244
|
+
await createTaxonomies(
|
|
245
|
+
[].concat( attachedMedia, featuredMedia ).filter((img) => { return img.data }),
|
|
246
|
+
toOptions,
|
|
247
|
+
'media',
|
|
248
|
+
spinner
|
|
249
|
+
)
|
|
250
|
+
}
|
|
457
251
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
{
|
|
463
|
-
...fromOptions,
|
|
464
|
-
url: mediaObj.source_url,
|
|
465
|
-
responseType: 'arraybuffer'
|
|
466
|
-
}
|
|
467
|
-
).then( (img) => { return new Blob([img.data]) });
|
|
468
|
-
|
|
469
|
-
mediaObj.data = mediaBlob;
|
|
252
|
+
// create pages
|
|
253
|
+
if( undefined === tax || tax.includes('pages')){
|
|
254
|
+
spinner.text = `Creating all pages to ${to.url}`;
|
|
255
|
+
await createTaxonomies( pages, toOptions, 'pages', spinner );
|
|
470
256
|
}
|
|
471
257
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
258
|
+
// create posts
|
|
259
|
+
if( undefined === tax || tax.includes('posts')){
|
|
260
|
+
spinner.text = `Creating all posts to ${to.url}`;
|
|
261
|
+
await createTaxonomies( posts, toOptions, 'posts', spinner );
|
|
262
|
+
}
|
|
475
263
|
|
|
476
|
-
//
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
'slug',
|
|
484
|
-
'meta',
|
|
485
|
-
'locations'
|
|
486
|
-
]
|
|
487
|
-
}, 'menus').then((navs) =>{
|
|
488
|
-
// filter out any menus not assigned to a location.
|
|
489
|
-
return navs.filter((menu) => {
|
|
490
|
-
return menu.locations.length
|
|
491
|
-
});
|
|
492
|
-
})
|
|
264
|
+
// create menus and navigation links
|
|
265
|
+
if( undefined === tax || tax.includes('menus')){
|
|
266
|
+
spinner.text = `Reconstructing navigation menus to ${to.url}`;
|
|
267
|
+
await createTaxonomies(menus, toOptions, 'menus', spinner);
|
|
268
|
+
await createTaxonomies(menuNavItems, toOptions, 'menu-items', spinner);
|
|
269
|
+
}
|
|
270
|
+
|
|
493
271
|
|
|
494
|
-
//
|
|
495
|
-
await createTaxonomies(
|
|
272
|
+
// update settings
|
|
273
|
+
await createTaxonomies(settings, toOptions, 'settings', spinner);
|
|
496
274
|
|
|
275
|
+
spinner.text = `Sync from ${from.url} to ${to.url} completed successfully.`
|
|
497
276
|
|
|
498
277
|
};
|
package/commands/test.js
CHANGED
|
@@ -2,24 +2,22 @@
|
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import axios from 'axios';
|
|
6
5
|
import fs from 'fs';
|
|
7
6
|
import loadConfig from '@wordpress/env/lib/config/load-config.js';
|
|
8
|
-
import
|
|
7
|
+
import os from 'os';
|
|
9
8
|
|
|
10
9
|
//var WP = require('wp-cli');
|
|
11
10
|
/**
|
|
12
11
|
* Internal dependencies
|
|
13
12
|
*/
|
|
14
13
|
import { runCLICmds, runCmd, projectPath } from '../lib/index.js';
|
|
14
|
+
import { CAWEB_OPTIONS, DIVI_OPTIONS } from '../lib/wordpress/index.js';
|
|
15
15
|
/*
|
|
16
16
|
import generateOverridesMD from '../admin.js';
|
|
17
|
-
import { CAWEB_OPTIONS, DIVI_OPTIONS } from '../options.js';
|
|
18
17
|
*/
|
|
19
18
|
/**
|
|
20
19
|
* Promisified dependencies
|
|
21
20
|
*/
|
|
22
|
-
import os from 'os';
|
|
23
21
|
|
|
24
22
|
/**
|
|
25
23
|
* Test code.
|
|
@@ -36,65 +34,21 @@ export default async function test({
|
|
|
36
34
|
} ) {
|
|
37
35
|
|
|
38
36
|
spinner.text = "Testing Code Functionality";
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// update post meta.
|
|
43
|
-
let id = 35;
|
|
44
|
-
let newId = 30
|
|
45
|
-
let tbl = `wp_term_taxonomy`;
|
|
37
|
+
const config = await loadConfig(path.resolve('.'));
|
|
38
|
+
const { workDirectoryPath } = config;
|
|
39
|
+
process.env.WP_CLI_CONFIG_PATH = path.join(config.workDirectoryPath, 'config.yml');
|
|
46
40
|
|
|
47
|
-
|
|
41
|
+
let cawebOptNames = CAWEB_OPTIONS;
|
|
42
|
+
|
|
43
|
+
let result = await runCmd(
|
|
48
44
|
'php',
|
|
49
45
|
[
|
|
50
|
-
|
|
51
|
-
'--ssh=docker:' + path.basename(config.workDirectoryPath) + '-cli-1',
|
|
52
|
-
`db query 'UPDATE ${tbl} SET term_taxonomy_id=${newId},term_id=${newId} WHERE term_id=${id}'`,
|
|
46
|
+
//'--info'
|
|
53
47
|
]
|
|
54
48
|
)
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
let u = 'http://danny.com/wp-json/wp/v2/pages/2827';
|
|
60
|
-
let result = await axios.request({
|
|
61
|
-
url: u,
|
|
62
|
-
method: 'POST',
|
|
63
|
-
data: {
|
|
64
|
-
title: 'Audio',
|
|
65
|
-
meta: {
|
|
66
|
-
_et_pb_use_builder: 'on'
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
headers: {
|
|
70
|
-
Authorization: 'Basic ' + Buffer.from(`admin:9RUG nPuo u581 cY2A uSJd FE2P`).toString('base64')
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
.then( async (res) => { return res.data; } )
|
|
74
|
-
.catch( async (err) => { return err; } )
|
|
75
|
-
|
|
76
|
-
*/
|
|
77
|
-
/*
|
|
78
|
-
let result = await runCmd(
|
|
79
|
-
'php',
|
|
80
|
-
[
|
|
81
|
-
//'-r',
|
|
82
|
-
path.join(projectPath, 'bin', 'wp-cli.phar'),
|
|
83
|
-
'--ssh=' + `docker:${path.basename(config.workDirectoryPath)}-cli-1`,
|
|
84
|
-
//'@dev',
|
|
85
|
-
//`db query '${q}'`,
|
|
86
|
-
'post list',
|
|
87
|
-
//'--info',
|
|
88
|
-
'--skip-themes',
|
|
89
|
-
//`--url=${new URL(u).origin}`
|
|
90
|
-
// '--format=json'
|
|
91
|
-
//'--skip-column-names',
|
|
92
|
-
//'post list path=/nas/content/live/cawebdev/ --http=https://dev.sites.ca.gov --url=https://dev.sites.ca.gov',
|
|
93
|
-
],
|
|
94
|
-
config
|
|
95
|
-
)*/
|
|
50
|
+
|
|
51
|
+
console.log( 'out', result.stdout.toString() );
|
|
52
|
+
console.log( 'err', result.stderr.toString() );
|
|
96
53
|
|
|
97
|
-
//console.log( 'Result', result.toString() );
|
|
98
|
-
//console.log( 'Output', result.stdout.toString() );
|
|
99
|
-
//console.log( 'Error', result.stderr.toString() );
|
|
100
54
|
};
|
package/lib/cli.js
CHANGED
|
@@ -259,6 +259,10 @@ export default function cli() {
|
|
|
259
259
|
.description('Sync changes from one destination to another.')
|
|
260
260
|
.argument('<from>', 'Target Site URL with current changes.')
|
|
261
261
|
.argument('<to>', 'Destination Site URL that should be synced.')
|
|
262
|
+
.addOption(new Option(
|
|
263
|
+
'-t,--tax [tax...]',
|
|
264
|
+
'Taxonomy that should be synced. Omitting this option will sync the full site.'
|
|
265
|
+
).choices(['pages', 'posts', 'media', 'menus']))
|
|
262
266
|
.allowUnknownOption(true)
|
|
263
267
|
.action( withSpinner(env.sync) )
|
|
264
268
|
|
|
@@ -269,7 +273,6 @@ export default function cli() {
|
|
|
269
273
|
//.addArgument(envArg)
|
|
270
274
|
.allowUnknownOption(true)
|
|
271
275
|
.action(withSpinner(env.test))
|
|
272
|
-
|
|
273
276
|
|
|
274
277
|
addWPEnvCommands();
|
|
275
278
|
|
package/lib/helpers.js
CHANGED
|
@@ -30,7 +30,7 @@ const appPath = path.resolve( projectPath, '../../../' );
|
|
|
30
30
|
*
|
|
31
31
|
* @returns {Promise}
|
|
32
32
|
*/
|
|
33
|
-
async function runCmd(cmd, args,opts = { stdio: 'inherit' }){
|
|
33
|
+
async function runCmd(cmd, args,opts = { stdio: ['inherit', 'pipe'] }){
|
|
34
34
|
// fix various commands.
|
|
35
35
|
switch (cmd) {
|
|
36
36
|
case 'npm':
|
package/lib/index.js
CHANGED
|
@@ -14,6 +14,20 @@ import {
|
|
|
14
14
|
appPath
|
|
15
15
|
} from './helpers.js';
|
|
16
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
|
+
|
|
17
31
|
export {
|
|
18
32
|
currentPath,
|
|
19
33
|
projectPath,
|
|
@@ -24,5 +38,16 @@ export {
|
|
|
24
38
|
wpYellow,
|
|
25
39
|
withSpinner,
|
|
26
40
|
runCmd,
|
|
27
|
-
runCLICmds
|
|
41
|
+
runCLICmds,
|
|
42
|
+
activateCAWeb,
|
|
43
|
+
configureCAWeb,
|
|
44
|
+
downloadSources,
|
|
45
|
+
configureDivi,
|
|
46
|
+
isDiviThemeActive,
|
|
47
|
+
configureWordPress,
|
|
48
|
+
isMultisite,
|
|
49
|
+
convertToMultisite,
|
|
50
|
+
generateHTAccess,
|
|
51
|
+
getTaxonomies,
|
|
52
|
+
createTaxonomies
|
|
28
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;
|
|
@@ -48,7 +52,29 @@ const withSpinner =
|
|
|
48
52
|
process.exit( 0 );
|
|
49
53
|
},
|
|
50
54
|
( error ) => {
|
|
51
|
-
|
|
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
|
+
let msg = data.message || 'An unknown error occurred.';
|
|
65
|
+
|
|
66
|
+
if( 401 === status ){
|
|
67
|
+
msg += `\nPlease check your ${terminalLink('Application Password', 'https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/')}`;
|
|
68
|
+
} else if( 403 === status ){
|
|
69
|
+
msg = 'Forbidden Request: A potentially unsafe operation has been detected in your request to this site';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
spinner.fail( msg )
|
|
73
|
+
process.exit( 1 );
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
}else if( error ){
|
|
52
78
|
// Error is an unknown error. That means there was a bug in our code.
|
|
53
79
|
spinner.fail(
|
|
54
80
|
typeof error === 'string' ? error : error.message
|
|
@@ -56,10 +82,13 @@ const withSpinner =
|
|
|
56
82
|
// Disable reason: Using console.error() means we get a stack trace.
|
|
57
83
|
console.error( error );
|
|
58
84
|
process.exit( 1 );
|
|
85
|
+
|
|
59
86
|
}else{
|
|
60
87
|
spinner.fail( 'An unknown error occurred.' );
|
|
61
88
|
process.exit( 1 );
|
|
89
|
+
|
|
62
90
|
}
|
|
91
|
+
|
|
63
92
|
}
|
|
64
93
|
);
|
|
65
94
|
};
|
|
@@ -0,0 +1,303 @@
|
|
|
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(/(”)|(″)|(“)/g, '') // replace double encoding
|
|
37
|
+
.replace(/(\s{2,})/g, ' ') // replace misc spacing // “
|
|
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
|
+
// The REST API doesn't allow passing the id, in order to maintain ID's we make an additional request to our plugin endpoint.
|
|
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 make a request to our plugin endpoint.
|
|
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
|
+
timeout: 7500,
|
|
236
|
+
data: 'media' === tax ? createMediaItem(obj) : obj,
|
|
237
|
+
url
|
|
238
|
+
})
|
|
239
|
+
.then( async (res) => { return res.data; } )
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* if the obj had an existing ID that didn't exist we make a request to our plugin endpoint to update the IDs.
|
|
244
|
+
*/
|
|
245
|
+
if( existingID ){
|
|
246
|
+
|
|
247
|
+
await axios.request({
|
|
248
|
+
...request,
|
|
249
|
+
url: `${request.url}/wp-json/caweb-dev/v1/sync`,
|
|
250
|
+
timeout: 7500,
|
|
251
|
+
data: {
|
|
252
|
+
id: results.id,
|
|
253
|
+
newId: existingID,
|
|
254
|
+
tax,
|
|
255
|
+
locations: 'menus' === tax ? obj.locations : []
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
.then( async (res) => { return res.data; } )
|
|
259
|
+
|
|
260
|
+
// update the API results ID, back to the existing ID from earlier.
|
|
261
|
+
results.id = existingID;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// add results to collection
|
|
265
|
+
collection = collection.concat( results );
|
|
266
|
+
|
|
267
|
+
let p = tax.charAt(0).toUpperCase() + tax.substring(1, tax.length - ( tax.endsWith('s') ? 1 : 0 ) );
|
|
268
|
+
|
|
269
|
+
if( undefined !== results && results.id ){
|
|
270
|
+
spinner.info(`${p}: ${results.id} was created.`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function createMediaItem( media ){
|
|
279
|
+
|
|
280
|
+
let fd = new FormData();
|
|
281
|
+
let featureMediaFile = new File(
|
|
282
|
+
[
|
|
283
|
+
media.data
|
|
284
|
+
],
|
|
285
|
+
media.source_url.split('/').pop(),
|
|
286
|
+
{
|
|
287
|
+
type: media.mime_type
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
fd.append('file', featureMediaFile );
|
|
291
|
+
fd.append('title', media.title);
|
|
292
|
+
fd.append('caption', media.caption);
|
|
293
|
+
fd.append('alt_text', media.alt_text);
|
|
294
|
+
fd.append('date', media.date);
|
|
295
|
+
|
|
296
|
+
return fd;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
export {
|
|
301
|
+
getTaxonomies,
|
|
302
|
+
createTaxonomies
|
|
303
|
+
}
|
package/lib/wordpress/index.js
CHANGED
|
@@ -2,6 +2,8 @@ import { activateCAWeb, configureCAWeb } from "./caweb.js";
|
|
|
2
2
|
import { downloadSources } from "./download-sources.js";
|
|
3
3
|
import { configureDivi, isDiviThemeActive } from "./divi.js";
|
|
4
4
|
import { configureWordPress, isMultisite, convertToMultisite, generateHTAccess } from "./wordpress.js";
|
|
5
|
+
import { getTaxonomies, createTaxonomies } from "./api.js";
|
|
6
|
+
import { CAWEB_OPTIONS, DIVI_OPTIONS } from './options.js';
|
|
5
7
|
|
|
6
8
|
export {
|
|
7
9
|
activateCAWeb,
|
|
@@ -12,5 +14,9 @@ export {
|
|
|
12
14
|
configureWordPress,
|
|
13
15
|
isMultisite,
|
|
14
16
|
convertToMultisite,
|
|
15
|
-
generateHTAccess
|
|
17
|
+
generateHTAccess,
|
|
18
|
+
getTaxonomies,
|
|
19
|
+
createTaxonomies,
|
|
20
|
+
CAWEB_OPTIONS,
|
|
21
|
+
DIVI_OPTIONS
|
|
16
22
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caweb/cli",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "CAWebPublishing Command Line Interface.",
|
|
5
5
|
"exports": "./lib/env.js",
|
|
6
6
|
"type": "module",
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@wordpress/env": "^9.
|
|
61
|
-
"@wordpress/scripts": "^27.
|
|
60
|
+
"@wordpress/env": "^9.4.0",
|
|
61
|
+
"@wordpress/scripts": "^27.3.0",
|
|
62
62
|
"accessibility-checker": "^3.1.67",
|
|
63
63
|
"autoprefixer": "^10.4.17",
|
|
64
64
|
"axios": "^1.6.7",
|
|
@@ -66,17 +66,17 @@
|
|
|
66
66
|
"commander": "^12.0.0",
|
|
67
67
|
"cross-spawn": "^7.0.3",
|
|
68
68
|
"css-loader": "^6.10.0",
|
|
69
|
-
"docker-compose": "^0.24.
|
|
69
|
+
"docker-compose": "^0.24.6",
|
|
70
70
|
"handlebars-loader": "^1.7.3",
|
|
71
|
-
"html-to-json-parser": "^2.0.
|
|
71
|
+
"html-to-json-parser": "^2.0.1",
|
|
72
72
|
"html-webpack-plugin": "^5.6.0",
|
|
73
73
|
"mini-css-extract-plugin": "^2.8.0",
|
|
74
74
|
"ora": "^8.0.1",
|
|
75
75
|
"postcss-loader": "^8.1.0",
|
|
76
76
|
"resolve-bin": "^1.0.1",
|
|
77
|
-
"sass-loader": "^14.1.
|
|
77
|
+
"sass-loader": "^14.1.1",
|
|
78
78
|
"terminal-link": "^3.0.0",
|
|
79
79
|
"url": "^0.11.3",
|
|
80
|
-
"webpack": "^5.90.
|
|
80
|
+
"webpack": "^5.90.3"
|
|
81
81
|
}
|
|
82
82
|
}
|