@caweb/cli 1.3.1 → 1.3.3
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 +19 -10
- package/commands/env/start.js +2 -4
- package/commands/sync.js +161 -108
- package/commands/test.js +12 -58
- package/configs/webpack.config.js +1 -1
- package/lib/cli.js +5 -2
- package/lib/spinner.js +7 -3
- package/lib/wordpress/api.js +39 -123
- package/lib/wordpress/index.js +4 -1
- package/package.json +2 -1
- package/bin/wp-cli.phar +0 -0
package/README.md
CHANGED
|
@@ -2,16 +2,12 @@
|
|
|
2
2
|
`caweb-cli` is a tool which rapidly sets up a local WordPress environment fully configured for the [CAWebPublishing Service](https://caweb.cdt.ca.gov/), allows for the creation of Gutenberg blocks with the CAWebPublishing template configurations, and much more. The cli will automatically generate the necessary [.wp-env.json](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/#wp-env-json) file, to override or add additional configuration options use the [.wp-env.override.json](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/#wp-env-override-json) file.
|
|
3
3
|
|
|
4
4
|
*`caweb-cli` is largely inspired by WordPress Packages major thanks to the whole WordPress team and community!*
|
|
5
|
-
The following WordPress packages are used:
|
|
6
|
-
[wp-env](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/)
|
|
7
|
-
[create-block](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/)
|
|
8
5
|
|
|
9
6
|
## Prerequisites
|
|
10
7
|
- Latest version of [Docker Desktop](https://www.docker.com/products/docker-desktop), which includes [Compose v2](https://docs.docker.com/compose/migrate/), is installed.
|
|
11
8
|
- <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
9
|
- <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
10
|
- git is installed.
|
|
14
|
-
- php is installed.
|
|
15
11
|
|
|
16
12
|
## Additional Features
|
|
17
13
|
- Downloads and configures the [CAWeb Theme](https://github.com/CAWebPublishing/CAWeb)
|
|
@@ -21,15 +17,13 @@ The following WordPress packages are used:
|
|
|
21
17
|
- phpMyAdmin development site starts at http://localhost:8080
|
|
22
18
|
- phpMyAdmin test site started at http://localhost:9090
|
|
23
19
|
- 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)
|
|
24
|
-
- Allows for syncing of WordPress instance via Rest API
|
|
20
|
+
- Allows for syncing of WordPress instance via Rest API, to maintain ID's please ensure [CAWebPublishing Development Toolbox](https://github.com/CAWebPublishing/caweb-dev/) plugin is installed.
|
|
25
21
|
- Adds config.yml to both cli containers, alias are added for each ssh connection.
|
|
26
22
|
<b>Example config.yml file</b>
|
|
27
23
|
<pre>
|
|
28
24
|
path: /var/www/html
|
|
29
25
|
apache_modules:
|
|
30
26
|
- mod_rewrite
|
|
31
|
-
@staging:
|
|
32
|
-
ssh: wpcli@staging.wp-cli.org
|
|
33
27
|
</pre>
|
|
34
28
|
|
|
35
29
|
|
|
@@ -81,7 +75,7 @@ Options:
|
|
|
81
75
|
</pre>
|
|
82
76
|
### `caweb run <container> [command...]`
|
|
83
77
|
<pre>
|
|
84
|
-
caweb run
|
|
78
|
+
caweb run <container> [command...]
|
|
85
79
|
|
|
86
80
|
Runs an arbitrary command in one of the underlying Docker containers. A double
|
|
87
81
|
dash can be used to pass arguments to the container without parsing them. This
|
|
@@ -166,7 +160,7 @@ Options:
|
|
|
166
160
|
|
|
167
161
|
### `caweb create-block [options] <slug>`
|
|
168
162
|
<pre>
|
|
169
|
-
caweb create-block [options]
|
|
163
|
+
caweb create-block [options] <slug>
|
|
170
164
|
|
|
171
165
|
Scaffold for WordPress plugin to register CA.gov Design System Block.
|
|
172
166
|
|
|
@@ -178,7 +172,7 @@ Options:
|
|
|
178
172
|
</pre>
|
|
179
173
|
### `caweb update-block [options] <slug>`
|
|
180
174
|
<pre>
|
|
181
|
-
caweb update-block [options]
|
|
175
|
+
caweb update-block [options] <slug>
|
|
182
176
|
|
|
183
177
|
Updates a CA.gov Design System Block.
|
|
184
178
|
|
|
@@ -188,3 +182,18 @@ Arguments:
|
|
|
188
182
|
Options:
|
|
189
183
|
-h, --help display help for command
|
|
190
184
|
</pre>
|
|
185
|
+
### `caweb sync <from> <to>`
|
|
186
|
+
In order for the sync process to work correctly, you must have a caweb.json file in the project root directory. For more information read [caweb.json](./docs/SYNC.MD) configuration.
|
|
187
|
+
<pre>
|
|
188
|
+
caweb sync <from> <to>
|
|
189
|
+
|
|
190
|
+
Sync changes from one destination to another.
|
|
191
|
+
|
|
192
|
+
Arguments:
|
|
193
|
+
from Target Site URL with current changes.
|
|
194
|
+
to Destination Site URL that should be synced.
|
|
195
|
+
|
|
196
|
+
Options:
|
|
197
|
+
-h, --help display help for command
|
|
198
|
+
-t,--tax [tax...] Taxonomy that should be synced. Omitting this option will sync the full site. (choices: "pages", "posts", "media", "menus")
|
|
199
|
+
</pre>
|
package/commands/env/start.js
CHANGED
|
@@ -149,11 +149,9 @@ export default async function start({
|
|
|
149
149
|
|
|
150
150
|
// Create an Application Password for the user.
|
|
151
151
|
/*
|
|
152
|
-
const devAppPwd = await
|
|
153
|
-
'
|
|
152
|
+
const devAppPwd = await runCLICmd(
|
|
153
|
+
'wp',
|
|
154
154
|
[
|
|
155
|
-
path.join(projectPath, 'bin', 'wp-cli.phar'),
|
|
156
|
-
`--ssh=docker:${path.basename(workDirectoryPath)}-cli-1`,
|
|
157
155
|
`user application-password create 1 caweb`,
|
|
158
156
|
'--porcelain'
|
|
159
157
|
]
|
package/commands/sync.js
CHANGED
|
@@ -26,12 +26,14 @@ const errors = [];
|
|
|
26
26
|
* @param {boolean} options.from Remote Site URL with current changes.
|
|
27
27
|
* @param {boolean} options.to Destination Site URL that should be synced.
|
|
28
28
|
* @param {boolean} options.debug True if debug mode is enabled.
|
|
29
|
+
* @param {Array} options.tax Taxonomy that should be synced.
|
|
29
30
|
*/
|
|
30
31
|
export default async function sync({
|
|
31
32
|
spinner,
|
|
32
33
|
debug,
|
|
33
34
|
from,
|
|
34
|
-
to
|
|
35
|
+
to,
|
|
36
|
+
tax
|
|
35
37
|
} ) {
|
|
36
38
|
|
|
37
39
|
const localFile = path.join(appPath, 'caweb.json');
|
|
@@ -77,146 +79,197 @@ export default async function sync({
|
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
let ssh = 'local' !== to ? to : `docker:${path.basename(workDirectoryPath)}-cli-1`;
|
|
81
82
|
|
|
82
83
|
to = serviceConfig.sync[to];
|
|
83
84
|
|
|
84
85
|
let toOptions = {
|
|
85
86
|
url: to.url,
|
|
86
|
-
ssh,
|
|
87
87
|
headers: {
|
|
88
88
|
Authorization: 'Basic ' + Buffer.from(`${to.user}:${to.pwd}`).toString('base64'),
|
|
89
89
|
'content-type': 'multipart/form-data',
|
|
90
90
|
'accept': '*/*'
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
|
|
94
|
+
let pages = [];
|
|
95
|
+
let posts = [];
|
|
96
|
+
let attachedMedia = [];
|
|
97
|
+
let featuredMedia = [];
|
|
98
|
+
let menus = [];
|
|
99
|
+
let menuNavItems = [];
|
|
100
|
+
|
|
95
101
|
spinner.text = `Getting Site Settings from ${from.url}`;
|
|
102
|
+
/**
|
|
103
|
+
* Site settings are always synced
|
|
104
|
+
*
|
|
105
|
+
* @see https://developer.wordpress.org/rest-api/reference/settings/
|
|
106
|
+
*/
|
|
96
107
|
let settings = await getTaxonomies({
|
|
97
108
|
...fromOptions,
|
|
98
109
|
fields: [
|
|
99
110
|
'show_on_front',
|
|
100
|
-
'page_on_front'
|
|
111
|
+
'page_on_front',
|
|
112
|
+
'posts_per_page'
|
|
101
113
|
],
|
|
102
114
|
}, 'settings')
|
|
103
115
|
|
|
104
|
-
// get all pages/posts
|
|
105
|
-
spinner.text = `Gathering all pages/posts from ${from.url}`;
|
|
106
|
-
let pages = await getTaxonomies(fromOptions);
|
|
107
|
-
let posts = await getTaxonomies(fromOptions, 'posts');
|
|
108
|
-
|
|
109
|
-
spinner.text = `Collecting all attached/featured images from ${from.url}`;
|
|
110
116
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
117
|
+
* If taxonomy is undefined then we sync everything, otherwise we only sync what's being requested.
|
|
118
|
+
* First we collect everything we need from the base, then we send the information to the target.
|
|
119
|
+
* If media is requested then we need to download posts/pages to get any attached/featured media.
|
|
114
120
|
*/
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
121
|
+
// collect pages
|
|
122
|
+
if( undefined === tax || tax.includes('pages') || tax.includes('media')){
|
|
123
|
+
// get all pages/posts
|
|
124
|
+
spinner.text = `Gathering all pages from ${from.url}`;
|
|
125
|
+
pages = await getTaxonomies(fromOptions);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// collect posts
|
|
129
|
+
if( undefined === tax || tax.includes('posts')|| tax.includes('media')){
|
|
130
|
+
// get all pages/posts
|
|
131
|
+
spinner.text = `Gathering all posts from ${from.url}`;
|
|
132
|
+
posts = await getTaxonomies(fromOptions, 'posts');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// collect media
|
|
136
|
+
if( undefined === tax || tax.includes('media')){
|
|
137
|
+
|
|
138
|
+
spinner.text = `Collecting all attached/featured images from ${from.url}`;
|
|
139
|
+
/**
|
|
140
|
+
* Media Library Handling
|
|
141
|
+
* 1) attached media items by default are only possible on pages.
|
|
142
|
+
* 2) featured media by default are only possible on posts.
|
|
143
|
+
*/
|
|
144
|
+
const mediaFields = [
|
|
145
|
+
'id',
|
|
146
|
+
'source_url',
|
|
147
|
+
'title',
|
|
148
|
+
'caption',
|
|
149
|
+
'alt_text',
|
|
150
|
+
'date',
|
|
151
|
+
'mime_type'
|
|
152
|
+
];
|
|
153
|
+
attachedMedia = await getTaxonomies({
|
|
154
|
+
...fromOptions,
|
|
155
|
+
fields: mediaFields,
|
|
156
|
+
parent: pages.map((p) => { return p.id })
|
|
157
|
+
},
|
|
158
|
+
'media'
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
featuredMedia = await getTaxonomies({
|
|
162
|
+
...fromOptions,
|
|
163
|
+
fields: mediaFields,
|
|
164
|
+
include: posts.map((p) => { if(p.featured_media){return p.featured_media } })
|
|
165
|
+
},
|
|
166
|
+
'media'
|
|
167
|
+
);
|
|
155
168
|
|
|
156
|
-
|
|
157
|
-
|
|
169
|
+
// before we can upload media files.
|
|
170
|
+
for( let mediaObj of [].concat( attachedMedia, featuredMedia ) ){
|
|
171
|
+
// generate a blob from the media object source_url.
|
|
172
|
+
const mediaBlob = await axios.request(
|
|
173
|
+
{
|
|
174
|
+
...fromOptions,
|
|
175
|
+
url: mediaObj.source_url,
|
|
176
|
+
responseType: 'arraybuffer'
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
.then( (img) => { return new Blob([img.data]) })
|
|
180
|
+
.catch( (error) => {
|
|
181
|
+
errors.push(`${mediaObj.source_url} could not be downloaded.`)
|
|
182
|
+
return false;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if( mediaBlob ){
|
|
186
|
+
mediaObj.data = mediaBlob;
|
|
187
|
+
}
|
|
158
188
|
}
|
|
159
189
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
'id',
|
|
167
|
-
'description',
|
|
168
|
-
'name',
|
|
169
|
-
'slug',
|
|
170
|
-
'meta',
|
|
171
|
-
'locations'
|
|
172
|
-
]
|
|
173
|
-
}, 'menus');
|
|
174
|
-
|
|
175
|
-
let menuNavItems = await getTaxonomies(
|
|
176
|
-
{
|
|
190
|
+
|
|
191
|
+
// collect menu and nav items.
|
|
192
|
+
if( undefined === tax || tax.includes('menus')){
|
|
193
|
+
spinner.text = `Collecting assigned navigation menus from ${from.url}`;
|
|
194
|
+
// get all menus and navigation links
|
|
195
|
+
menus = await getTaxonomies({
|
|
177
196
|
...fromOptions,
|
|
178
197
|
fields: [
|
|
179
198
|
'id',
|
|
180
|
-
'title',
|
|
181
|
-
'url',
|
|
182
|
-
'status',
|
|
183
|
-
'attr_title',
|
|
184
199
|
'description',
|
|
185
|
-
'
|
|
186
|
-
'
|
|
187
|
-
'object',
|
|
188
|
-
'object_id',
|
|
189
|
-
'parent',
|
|
190
|
-
'menu_order',
|
|
191
|
-
'target',
|
|
192
|
-
'classes',
|
|
193
|
-
'xfn',
|
|
200
|
+
'name',
|
|
201
|
+
'slug',
|
|
194
202
|
'meta',
|
|
195
|
-
'
|
|
196
|
-
]
|
|
197
|
-
|
|
198
|
-
},
|
|
199
|
-
'menu-items'
|
|
200
|
-
)
|
|
203
|
+
'locations'
|
|
204
|
+
]
|
|
205
|
+
}, 'menus');
|
|
201
206
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
207
|
+
menuNavItems = await getTaxonomies(
|
|
208
|
+
{
|
|
209
|
+
...fromOptions,
|
|
210
|
+
fields: [
|
|
211
|
+
'id',
|
|
212
|
+
'title',
|
|
213
|
+
'url',
|
|
214
|
+
'status',
|
|
215
|
+
'attr_title',
|
|
216
|
+
'description',
|
|
217
|
+
'type',
|
|
218
|
+
'type_label',
|
|
219
|
+
'object',
|
|
220
|
+
'object_id',
|
|
221
|
+
'parent',
|
|
222
|
+
'menu_order',
|
|
223
|
+
'target',
|
|
224
|
+
'classes',
|
|
225
|
+
'xfn',
|
|
226
|
+
'meta',
|
|
227
|
+
'menus'
|
|
228
|
+
],
|
|
229
|
+
menus: menus.map((menu) => { return menu.id; })
|
|
230
|
+
},
|
|
231
|
+
'menu-items'
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Now we have all the data we can begin to create the taxonomies on the target.
|
|
238
|
+
*
|
|
239
|
+
* Creation order is important otherwise missing dependencies will cause errors to trigger.
|
|
240
|
+
* We create media first, then pages, then posts, then menus and navigation links.
|
|
241
|
+
*/
|
|
206
242
|
|
|
207
243
|
// create media attachments
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
244
|
+
if( undefined === tax || tax.includes('media')){
|
|
245
|
+
spinner.text = `Uploading media files to ${to.url}`;
|
|
246
|
+
await createTaxonomies(
|
|
247
|
+
[].concat( attachedMedia, featuredMedia ).filter((img) => { return img.data }),
|
|
248
|
+
toOptions,
|
|
249
|
+
'media',
|
|
250
|
+
spinner
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// create pages
|
|
255
|
+
if( undefined === tax || tax.includes('pages')){
|
|
256
|
+
spinner.text = `Creating all pages to ${to.url}`;
|
|
257
|
+
await createTaxonomies( pages, toOptions, 'pages', spinner );
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// create posts
|
|
261
|
+
if( undefined === tax || tax.includes('posts')){
|
|
262
|
+
spinner.text = `Creating all posts to ${to.url}`;
|
|
263
|
+
await createTaxonomies( posts, toOptions, 'posts', spinner );
|
|
264
|
+
}
|
|
265
|
+
|
|
216
266
|
// create menus and navigation links
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
267
|
+
if( undefined === tax || tax.includes('menus')){
|
|
268
|
+
spinner.text = `Reconstructing navigation menus to ${to.url}`;
|
|
269
|
+
await createTaxonomies(menus, toOptions, 'menus', spinner);
|
|
270
|
+
await createTaxonomies(menuNavItems, toOptions, 'menu-items', spinner);
|
|
271
|
+
}
|
|
272
|
+
|
|
220
273
|
|
|
221
274
|
// update settings
|
|
222
275
|
await createTaxonomies(settings, toOptions, 'settings', spinner);
|
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,17 +259,20 @@ 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
|
|
|
265
269
|
// Test Command.
|
|
266
270
|
// Ensure this is commented out.
|
|
267
|
-
|
|
271
|
+
program.command('test')
|
|
268
272
|
.description('Test commands on a WordPress environment')
|
|
269
273
|
//.addArgument(envArg)
|
|
270
274
|
.allowUnknownOption(true)
|
|
271
275
|
.action(withSpinner(env.test))
|
|
272
|
-
*/
|
|
273
276
|
|
|
274
277
|
addWPEnvCommands();
|
|
275
278
|
|
package/lib/spinner.js
CHANGED
|
@@ -61,15 +61,19 @@ const withSpinner =
|
|
|
61
61
|
){
|
|
62
62
|
|
|
63
63
|
let { data, status } = error.response;
|
|
64
|
-
|
|
64
|
+
let msg = data.message || 'An unknown error occurred.';
|
|
65
|
+
|
|
65
66
|
if( 401 === status ){
|
|
66
|
-
|
|
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';
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
spinner.fail(
|
|
72
|
+
spinner.fail( msg )
|
|
70
73
|
process.exit( 1 );
|
|
71
74
|
|
|
72
75
|
|
|
76
|
+
|
|
73
77
|
}else if( error ){
|
|
74
78
|
// Error is an unknown error. That means there was a bug in our code.
|
|
75
79
|
spinner.fail(
|
package/lib/wordpress/api.js
CHANGED
|
@@ -4,21 +4,29 @@
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import axios from 'axios';
|
|
6
6
|
import terminalLink from 'terminal-link';
|
|
7
|
-
|
|
7
|
+
import axiosRetry from 'axios-retry';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Internal dependencies
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
13
|
-
projectPath,
|
|
14
|
-
runCmd
|
|
15
|
-
} from '../index.js';
|
|
16
|
-
|
|
17
12
|
const endpoint = '/wp-json/wp/v2';
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
const syncEndpoint = '/wp-json/caweb/v1/sync';
|
|
14
|
+
|
|
15
|
+
// Axios Configurations.
|
|
16
|
+
axiosRetry(axios,
|
|
17
|
+
{
|
|
18
|
+
retries: 3, // attempt requests 3 times
|
|
19
|
+
shouldResetTimeout: true, // reset timeout on retries
|
|
20
|
+
retryCondition: (error) => {
|
|
21
|
+
if( error.response && error.response.status === 500 ){
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
}, // retry on any network or 5xx error
|
|
25
|
+
retryDelay: (retryCount, error ) => {
|
|
26
|
+
return 5000 // retry delay
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
);
|
|
22
30
|
|
|
23
31
|
function processData( data ){
|
|
24
32
|
/**
|
|
@@ -106,12 +114,13 @@ async function getTaxonomies( request, tax = 'pages' ){
|
|
|
106
114
|
urlParams.push( 'menus=' + processUrlParam( request.menus ) );
|
|
107
115
|
}
|
|
108
116
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
/**
|
|
118
|
+
* The per_page parameter is capped at 100.
|
|
119
|
+
*
|
|
120
|
+
* @see https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/
|
|
121
|
+
*/
|
|
122
|
+
request.per_page = request.per_page && request.per_page <= 100 ? request.per_page : 100;
|
|
123
|
+
urlParams.push( 'per_page=' + request.per_page );
|
|
115
124
|
|
|
116
125
|
|
|
117
126
|
let collection = [];
|
|
@@ -174,7 +183,7 @@ async function createTaxonomies( taxData, request, tax = 'pages', spinner ){
|
|
|
174
183
|
let url = `${request.url}${endpoint}/${tax}`;
|
|
175
184
|
let existingID = false;
|
|
176
185
|
|
|
177
|
-
// in order to maintain ID's
|
|
186
|
+
// 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
187
|
if( obj.id ){
|
|
179
188
|
// first we check if the ID exist
|
|
180
189
|
let idExists = await axios.request(
|
|
@@ -201,9 +210,8 @@ async function createTaxonomies( taxData, request, tax = 'pages', spinner ){
|
|
|
201
210
|
.catch(error => {return false;})
|
|
202
211
|
|
|
203
212
|
}
|
|
204
|
-
|
|
205
213
|
/**
|
|
206
|
-
* if the ID doesn't exist we save it so we can
|
|
214
|
+
* if the ID doesn't exist we save it so we can make a request to our plugin endpoint.
|
|
207
215
|
* if it does exist update endpoint url so we update the existing item.
|
|
208
216
|
*/
|
|
209
217
|
if( ! idExists ){
|
|
@@ -237,114 +245,22 @@ async function createTaxonomies( taxData, request, tax = 'pages', spinner ){
|
|
|
237
245
|
})
|
|
238
246
|
.then( async (res) => { return res.data; } )
|
|
239
247
|
|
|
240
|
-
|
|
241
248
|
/**
|
|
242
|
-
* if the obj had an existing ID that didn't exist
|
|
249
|
+
* if the obj had an existing ID that didn't exist we make a request to our plugin endpoint to update the IDs.
|
|
243
250
|
*/
|
|
244
251
|
if( existingID ){
|
|
245
|
-
let post_tbl = 'wp_posts';
|
|
246
|
-
let post_meta_tbl = 'wp_postmeta';
|
|
247
|
-
let term_taxonomy_tbl = 'wp_term_taxonomy';
|
|
248
|
-
let terms_tbl = 'wp_terms';
|
|
249
|
-
|
|
250
|
-
const cmd = [
|
|
251
|
-
path.join(projectPath, 'bin', 'wp-cli.phar'),
|
|
252
|
-
`--ssh=${request.ssh}`,
|
|
253
|
-
'--skip-themes',
|
|
254
|
-
`--url=${new URL(request.url).origin}`
|
|
255
|
-
];
|
|
256
|
-
|
|
257
|
-
// if taxonomy is page/post/media.
|
|
258
|
-
if( ['pages', 'posts', 'media'].includes(tax) ){
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Since the REST API doesn't detected trashed posts/pages.
|
|
263
|
-
* We have to do a drop before we can do an update.
|
|
264
|
-
* Since trashed posts/pages stay in the database
|
|
265
|
-
*/
|
|
266
|
-
// drop post
|
|
267
|
-
/*await runCmd(
|
|
268
|
-
'php',
|
|
269
|
-
[
|
|
270
|
-
...cmd,
|
|
271
|
-
'--skip-column-names',
|
|
272
|
-
`db query 'DELETE FROM ${post_tbl} WHERE ID=${existingID}'`,
|
|
273
|
-
]
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
// drop post meta.
|
|
277
|
-
await runCmd(
|
|
278
|
-
'php',
|
|
279
|
-
[
|
|
280
|
-
...cmd,
|
|
281
|
-
'--skip-column-names',
|
|
282
|
-
`db query 'DELETE FROM ${post_meta_tbl} WHERE post_id=${existingID}'`,
|
|
283
|
-
]
|
|
284
|
-
)*/
|
|
285
|
-
|
|
286
|
-
// now we can update the new ID with the existing ID.
|
|
287
|
-
await runCmd(
|
|
288
|
-
'php',
|
|
289
|
-
[
|
|
290
|
-
...cmd,
|
|
291
|
-
'--skip-column-names',
|
|
292
|
-
`db query 'UPDATE ${post_tbl} SET ID=${existingID} WHERE ID=${results.id}'`,
|
|
293
|
-
]
|
|
294
|
-
)
|
|
295
|
-
// update post meta.
|
|
296
|
-
await runCmd(
|
|
297
|
-
'php',
|
|
298
|
-
[
|
|
299
|
-
...cmd,
|
|
300
|
-
'--skip-column-names',
|
|
301
|
-
`db query 'UPDATE ${post_meta_tbl} SET post_id=${existingID} WHERE post_id=${results.id}'`,
|
|
302
|
-
]
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
// if taxonomy is menu.
|
|
306
|
-
}else if( 'menus' === tax ){
|
|
307
|
-
// update the terms table.
|
|
308
|
-
await runCmd(
|
|
309
|
-
'php',
|
|
310
|
-
[
|
|
311
|
-
...cmd,
|
|
312
|
-
'--skip-column-names',
|
|
313
|
-
`db query 'UPDATE ${terms_tbl} SET term_id=${existingID} WHERE term_id=${results.id}'`,
|
|
314
|
-
]
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
// update the term taxonomy table.
|
|
318
|
-
await runCmd(
|
|
319
|
-
'php',
|
|
320
|
-
[
|
|
321
|
-
...cmd,
|
|
322
|
-
'--skip-column-names',
|
|
323
|
-
`db query 'UPDATE ${term_taxonomy_tbl} SET term_taxonomy_id=${existingID},term_id=${existingID} WHERE term_id=${results.id}'`,
|
|
324
|
-
]
|
|
325
|
-
)
|
|
326
252
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
'option patch update',
|
|
339
|
-
`theme_mods_CAWeb`,
|
|
340
|
-
'nav_menu_locations',
|
|
341
|
-
location,
|
|
342
|
-
existingID
|
|
343
|
-
]
|
|
344
|
-
)
|
|
345
|
-
})
|
|
346
|
-
|
|
347
|
-
}
|
|
253
|
+
await axios.request({
|
|
254
|
+
...request,
|
|
255
|
+
url: `${request.url}${syncEndpoint}`,
|
|
256
|
+
data: {
|
|
257
|
+
id: results.id,
|
|
258
|
+
newId: existingID,
|
|
259
|
+
tax,
|
|
260
|
+
locations: 'menus' === tax ? obj.locations : []
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
.then( async (res) => { return res.data; } )
|
|
348
264
|
|
|
349
265
|
// update the API results ID, back to the existing ID from earlier.
|
|
350
266
|
results.id = existingID;
|
package/lib/wordpress/index.js
CHANGED
|
@@ -3,6 +3,7 @@ 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
5
|
import { getTaxonomies, createTaxonomies } from "./api.js";
|
|
6
|
+
import { CAWEB_OPTIONS, DIVI_OPTIONS } from './options.js';
|
|
6
7
|
|
|
7
8
|
export {
|
|
8
9
|
activateCAWeb,
|
|
@@ -15,5 +16,7 @@ export {
|
|
|
15
16
|
convertToMultisite,
|
|
16
17
|
generateHTAccess,
|
|
17
18
|
getTaxonomies,
|
|
18
|
-
createTaxonomies
|
|
19
|
+
createTaxonomies,
|
|
20
|
+
CAWEB_OPTIONS,
|
|
21
|
+
DIVI_OPTIONS
|
|
19
22
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caweb/cli",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "CAWebPublishing Command Line Interface.",
|
|
5
5
|
"exports": "./lib/env.js",
|
|
6
6
|
"type": "module",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"accessibility-checker": "^3.1.67",
|
|
63
63
|
"autoprefixer": "^10.4.17",
|
|
64
64
|
"axios": "^1.6.7",
|
|
65
|
+
"axios-retry": "^4.0.0",
|
|
65
66
|
"chalk": "^5.3.0",
|
|
66
67
|
"commander": "^12.0.0",
|
|
67
68
|
"cross-spawn": "^7.0.3",
|
package/bin/wp-cli.phar
DELETED
|
Binary file
|