@caweb/cli 1.5.0 → 1.5.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/lib/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
-
4
+ //const wpenv_cli = require('@wordpress/env/lib/cli');
5
5
  import path from 'path';
6
6
  import fs from 'fs';
7
7
  import { Command, Argument, Option } from 'commander';
@@ -39,13 +39,17 @@ const envArg = new Argument('[environment]', 'Which environment to use.')
39
39
  ])
40
40
  .default('development');
41
41
 
42
+
43
+ /**
44
+ * Adds commands for webpack
45
+ */
42
46
  function addWebpackCmds(){
43
47
 
44
48
  // Build Command.
45
49
  program.command('build')
46
- .description('Builds the current project.')
47
- .allowUnknownOption(true)
48
- .action(env.webpack)
50
+ .description('Builds the current project.')
51
+ .allowUnknownOption(true)
52
+ .action(env.webpack)
49
53
 
50
54
 
51
55
  // Serve Command.
@@ -127,6 +131,7 @@ function addWPEnvCommands(){
127
131
 
128
132
  // Start command.
129
133
  program.command('start')
134
+ .alias('launch')
130
135
  .description(
131
136
  `Starts two CAWebPublishing WordPress instances\ndevelopment on port http://localhost:8888 (override with WP_ENV_PORT)\ntests on port http://localhost:8889 (override with WP_ENV_TESTS_PORT).`
132
137
  )
@@ -174,6 +179,7 @@ function addWPEnvCommands(){
174
179
 
175
180
  // Destroy Command.
176
181
  program.command('destroy')
182
+ .alias('shutdown')
177
183
  .description(
178
184
  'Deletes docker containers, volumes, and networks associated with the CAWebPublishing instances and removes local files.'
179
185
  )
@@ -255,7 +261,6 @@ function addWPEnvCommands(){
255
261
 
256
262
  export default function cli() {
257
263
 
258
-
259
264
  program
260
265
  .name('caweb')
261
266
  .usage( '<command>' )
@@ -301,15 +306,35 @@ export default function cli() {
301
306
  // Update a Design System Block Command.
302
307
  program.command('sync')
303
308
  .description('Sync changes from one WordPress instance to another.')
304
- .argument('<from>', 'Target Site URL.')
305
- .argument('<to>', 'Destination Site URL.')
309
+ .argument('[target]', 'Target Site URL.')
310
+ .argument('[dest]', 'Destination Site URL.')
311
+ .addOption(new Option(
312
+ '--interactive',
313
+ 'Runs the sync process with prompts'
314
+ ))
315
+ .addOption(
316
+ new Option(
317
+ '-t,--tax <tax...>',
318
+ 'Taxonomy that should be synced. Default is full site sync.'
319
+ )
320
+ .choices(['media', 'menus', 'pages', 'posts', 'settings'])
321
+ .default(['media', 'menus', 'pages', 'posts', 'settings'])
322
+ )
306
323
  .addOption(new Option(
307
- '-t,--tax [tax...]',
308
- 'Taxonomy that should be synced. Omitting this option will sync the full site.'
309
- ).choices(['pages', 'posts', 'media', 'menus', 'settings']))
324
+ '--media-ids <ids...>',
325
+ 'Sync specific Media IDs.'
326
+ ))
327
+ .addOption(new Option(
328
+ '--menu-ids <ids...>',
329
+ 'Sync specific Menu IDs.'
330
+ ))
331
+ .addOption(new Option(
332
+ '--page-ids <ids...>',
333
+ 'Sync specific Page IDs.'
334
+ ))
310
335
  .addOption(new Option(
311
- '-i,--include [include...]',
312
- 'IDs to of taxonomies to include. Omitting this option will sync all items for given taxonomy.'
336
+ '--post-ids <ids...>',
337
+ 'Sync specific Post IDs.'
313
338
  ))
314
339
  .allowUnknownOption(true)
315
340
  .action( withSpinner(env.sync) )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caweb/cli",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "CAWebPublishing Command Line Interface.",
5
5
  "exports": "./lib/env.js",
6
6
  "type": "module",
@@ -56,15 +56,17 @@
56
56
  }
57
57
  },
58
58
  "dependencies": {
59
- "@caweb/webpack": "^1.0.0",
60
- "@wordpress/create-block": "^4.45.0",
61
- "@wordpress/env": "^10.2.0",
59
+ "@caweb/webpack": "^1.0.2",
60
+ "@inquirer/prompts": "^5.1.3",
61
+ "@wordpress/create-block": "^4.46.0",
62
+ "@wordpress/env": "^10.3.0",
62
63
  "axios": "^1.7.2",
63
64
  "axios-retry": "^4.4.1",
64
65
  "chalk": "^5.3.0",
65
66
  "commander": "^12.1.0",
66
67
  "cross-spawn": "^7.0.3",
67
68
  "docker-compose": "^0.24.8",
69
+ "inquirer-select-pro": "^1.0.0-alpha.6",
68
70
  "ora": "^8.0.1",
69
71
  "resolve-bin": "^1.0.1",
70
72
  "terminal-link": "^3.0.0"
package/commands/sync.js DELETED
@@ -1,402 +0,0 @@
1
- /**
2
- * External dependencies
3
- */
4
- import path from 'path';
5
- import fs from 'fs';
6
- import loadConfig from '@wordpress/env/lib/config/load-config.js';
7
- import axios from 'axios';
8
-
9
- /**
10
- * Internal dependencies
11
- */
12
- import {
13
- appPath,
14
- getTaxonomies,
15
- createTaxonomies
16
- } from '../lib/index.js';
17
-
18
- const errors = [];
19
-
20
- /**
21
- * Return all parent items for a given set of ids.
22
- *
23
- * @async
24
- * @param {Array} ids
25
- * @param {*} request
26
- * @param {string} [tax='pages']
27
- * @returns {unknown}
28
- */
29
- async function getParentItems( objects, request, tax = 'pages' ){
30
- let parentItemsObjects = [];
31
- let objectParentIds = objects.map((obj) => { return obj.parent; }).filter(n => n);
32
-
33
- while( objectParentIds.length > 0 ){
34
-
35
- // if we have parent ids, we have to collect any parent items.
36
- let parentItems = await getTaxonomies({
37
- ...request,
38
- orderby: 'parent',
39
- embed: true,
40
- include: objectParentIds
41
- }, tax);
42
-
43
- // if we have parent items, we have to add to the items array.
44
- if( parentItems ){
45
- parentItemsObjects = parentItemsObjects.concat( parentItems );
46
- }
47
-
48
- // if the parent items have parent ids, we have to save those for the next run.
49
- objectParentIds = parentItems.map((obj) => { return obj.parent; }).filter((id) => { return id !== 0; })
50
- }
51
-
52
- return objects.concat( parentItemsObjects ).sort( (a,b) => a.parent - b.parent );
53
- }
54
-
55
- /**
56
- * Sync Environments.
57
- *
58
- * @see https://developer.wordpress.org/rest-api/reference/
59
- *
60
- * @param {Object} options
61
- * @param {Object} options.spinner A CLI spinner which indicates progress.
62
- * @param {boolean} options.from Remote Site URL with current changes.
63
- * @param {boolean} options.to Destination Site URL that should be synced.
64
- * @param {boolean} options.debug True if debug mode is enabled.
65
- * @param {Array} options.tax Taxonomy that should be synced.
66
- * @param {Array} options.include Include specific IDs only.
67
- */
68
- export default async function sync({
69
- spinner,
70
- debug,
71
- from,
72
- to,
73
- tax,
74
- include
75
- } ) {
76
-
77
- const localFile = path.join(appPath, 'caweb.json');
78
- const {workDirectoryPath} = await loadConfig(path.resolve('.'));
79
-
80
- process.env.WP_CLI_CONFIG_PATH = path.join(workDirectoryPath, 'config.yml');
81
-
82
- // if caweb.json file doesn't exist we don't do anything
83
- if( ! fs.existsSync(localFile) ){
84
- spinner.fail('caweb.json file not found.')
85
- process.exit(1)
86
- }
87
-
88
- // read configuration file
89
- const serviceConfig = JSON.parse( fs.readFileSync(localFile) );
90
-
91
- /**
92
- * make sure instances are in the file.
93
- * must have sync property
94
- * each instance has to have a url, user, pwd property
95
- */
96
- if(
97
- ! serviceConfig.sync ||
98
- ! serviceConfig.sync[from] ||
99
- ! serviceConfig.sync[from].url ||
100
- ! serviceConfig.sync[from].user ||
101
- ! serviceConfig.sync[from].pwd ||
102
- ! serviceConfig.sync[to] ||
103
- ! serviceConfig.sync[to].url ||
104
- ! serviceConfig.sync[to].user ||
105
- ! serviceConfig.sync[to].pwd
106
- ){
107
- spinner.fail(`caweb.json is not configured properly for ${from} and ${to}.`);
108
- process.exit(1)
109
- }
110
-
111
- // get instance data
112
- from = serviceConfig.sync[from];
113
- let fromOptions = {
114
- url: from.url,
115
- headers: {
116
- Authorization: 'Basic ' + Buffer.from(`${from.user}:${from.pwd}`).toString('base64')
117
- }
118
- }
119
-
120
-
121
- to = serviceConfig.sync[to];
122
-
123
- let toOptions = {
124
- url: to.url,
125
- headers: {
126
- Authorization: 'Basic ' + Buffer.from(`${to.user}:${to.pwd}`).toString('base64'),
127
- 'content-type': 'multipart/form-data',
128
- 'accept': '*/*'
129
- }
130
- }
131
-
132
- /**
133
- * Sync Process
134
- * If taxonomy is undefined then we don't sync that section.
135
- *
136
- * 1) We collect media if the taxonomy is undefined or includes media, pages, posts.
137
- * 2) We only collect settings if the taxonomy is undefined or it's set to settings.
138
- * 3) We only collect pages if the taxonomy is undefined or it's set to pages.
139
- * 4) We only collect posts if the taxonomy is undefined or it's set to posts.
140
- * 5) We only collect menus if the taxonomy is undefined or it's set to menus.
141
- * - We also collect menu items if menus are collected.
142
- */
143
- let settings = [];
144
- let mediaLibrary = [];
145
- let media = [];
146
- let pages = [];
147
- let posts = [];
148
- let menus = [];
149
- let menuNavItems = [];
150
-
151
- // Media Library.
152
- if( undefined === tax || tax.includes('media', 'pages', 'posts') ){
153
- spinner.text = `Collecting Media Library ${from.url}`;
154
- mediaLibrary = await getTaxonomies({
155
- ...fromOptions,
156
- fields: [
157
- 'id',
158
- 'source_url',
159
- 'title',
160
- 'caption',
161
- 'alt_text',
162
- 'date',
163
- 'mime_type',
164
- 'post',
165
- 'media_details'
166
- ],
167
- include: tax && tax.includes('media') && include ? include.join(',') : null
168
- },
169
- 'media'
170
- );
171
- }
172
-
173
- // Site Settings.
174
- if( undefined === tax || tax.includes('settings') ){
175
- spinner.text = `Collecting Site Settings from ${from.url}`;
176
- settings = await getTaxonomies({
177
- ...fromOptions,
178
- fields: [
179
- 'title',
180
- 'description',
181
- 'show_on_front',
182
- 'page_on_front',
183
- 'posts_per_page'
184
- ],
185
- }, 'settings')
186
-
187
- }
188
-
189
- // Pages.
190
- if( undefined === tax || tax.includes('pages') ){
191
- // get all pages/posts
192
- spinner.text = `Collecting pages from ${from.url}`;
193
- pages = await getTaxonomies({
194
- ...fromOptions,
195
- orderby: 'parent',
196
- embed: true,
197
- include: tax && include ? include.join(',') : null
198
- },
199
- 'pages'
200
- );
201
-
202
- // we only do this if specific ids were requested.
203
- if( include ){
204
- // if we have parent ids, we have to collect any parent items.
205
- pages = await getParentItems(
206
- pages,
207
- fromOptions,
208
- 'pages'
209
- )
210
- }
211
- }
212
-
213
- // Posts.
214
- if( undefined === tax || tax.includes('posts') ){
215
- // get all pages/posts
216
- spinner.text = `Collecting all posts from ${from.url}`;
217
- posts = await getTaxonomies({
218
- ...fromOptions,
219
- orderby: 'parent',
220
- include: tax && include ? include.join(',') : null
221
- },
222
- 'posts'
223
- );
224
-
225
- // we only do this if specific ids were requested.
226
- if( include ){
227
- // if we have parent ids, we have to collect any parent items.
228
- posts = await getParentItems(
229
- posts,
230
- fromOptions,
231
- 'posts'
232
- )
233
- }
234
- }
235
-
236
- /**
237
- * Media Library Handling
238
- *
239
- * We iterate thru the media library to identify which media should be synced.
240
- * 1) attached media items by default are only possible on pages.
241
- * 2) featured media by default are only possible on posts.
242
- * 3) save any linked media in the content of pages and posts.
243
- */
244
- for( let m of mediaLibrary ){
245
- // remove any new line characters.
246
- m.source_url = m.source_url.replace('\n','');
247
-
248
- // check if the media is attached to a page.
249
- // if tax is undefined, we collect all media attached to posts and pages.
250
- // if tax is defined, if include is undefined or media is matches post/page, we collect the media.
251
- if( ( ! tax && m.post ) ||
252
- ( tax &&
253
- ( undefined === include || include.includes( m.id.toString() ) )
254
- ) ){
255
- media.push( m );
256
-
257
- // we don't have to check any further.
258
- continue;
259
- }
260
-
261
- for( let p of [].concat( pages, posts ) ){
262
- // if the media is featured on a post or linked in the content.
263
- if( p.featured_media === m.id ||
264
- p.content.rendered.match( new RegExp(`src=&#8221;(${m.source_url}.*)&#8221;`, 'g') )){
265
- media.push( m );
266
- }
267
- }
268
- }
269
-
270
- // filter any duplicate media.
271
- media = media.filter((m, index, self) => { return index === self.findIndex((t) => { return t.id === m.id; })} );
272
- let i = 0;
273
-
274
- // before we can upload media files we have to generate the media blob data.
275
- for( let m of media ){
276
- if( debug ){
277
- i++;
278
- spinner.info(`Media ID ${m.id} Collected: ${i}/${media.length}`)
279
- }
280
-
281
- const mediaBlob = await axios.request(
282
- {
283
- ...fromOptions,
284
- url: m.source_url,
285
- responseType: 'arraybuffer'
286
- }
287
- )
288
- .then( (img) => { return new Blob([img.data]) })
289
- .catch( (error) => {
290
- errors.push(`${m.source_url} could not be downloaded.`)
291
- return false;
292
- });
293
-
294
- if( mediaBlob ){
295
- m.data = mediaBlob;
296
- }
297
- }
298
-
299
- // this has to be done after we have the media data.
300
- // Lets replace the url references in the content of the pages and posts.
301
- for( let p of [].concat( pages, posts ) ){
302
- p.content.rendered = p.content.rendered.replace( new RegExp(from.url, 'g'), to.url );
303
- }
304
-
305
-
306
- // Menu and Nav Items.
307
- if( undefined === tax || tax.includes('menus')){
308
- spinner.text = `Collecting assigned navigation menus from ${from.url}`;
309
- // get all menus and navigation links
310
- menus = await getTaxonomies({
311
- ...fromOptions,
312
- fields: [
313
- 'id',
314
- 'description',
315
- 'name',
316
- 'slug',
317
- 'meta',
318
- 'locations'
319
- ],
320
- include: tax && include ? include.join(',') : null
321
- }, 'menus');
322
-
323
- menuNavItems = await getTaxonomies(
324
- {
325
- ...fromOptions,
326
- fields: [
327
- 'id',
328
- 'title',
329
- 'url',
330
- 'status',
331
- 'attr_title',
332
- 'description',
333
- 'type',
334
- 'type_label',
335
- 'object',
336
- 'object_id',
337
- 'parent',
338
- 'menu_order',
339
- 'target',
340
- 'classes',
341
- 'xfn',
342
- 'meta',
343
- 'menus'
344
- ],
345
- menus: menus.map((menu) => { return menu.id; })
346
- },
347
- 'menu-items'
348
- )
349
-
350
- }
351
-
352
- /**
353
- * Now we have all the data we can begin to create the taxonomies on the target.
354
- *
355
- * Import Order is important otherwise missing dependencies will cause errors to trigger
356
- * 1) Media
357
- * 2) Pages
358
- * 3) Posts
359
- * 4) Menus & Navigation Links
360
- * 5) Settings
361
- */
362
-
363
- // Media.
364
- if( media ){
365
- spinner.text = `Uploading media files to ${to.url}`;
366
- await createTaxonomies(
367
- media.filter((img) => { return img.data }),
368
- toOptions,
369
- 'media',
370
- spinner
371
- )
372
- }
373
-
374
- // Pages.
375
- if( pages ){
376
- spinner.text = `Creating all pages to ${to.url}`;
377
- await createTaxonomies( pages, toOptions, 'pages', spinner );
378
- }
379
-
380
- // Posts.
381
- if( posts ){
382
- spinner.text = `Creating all posts to ${to.url}`;
383
- await createTaxonomies( posts, toOptions, 'posts', spinner );
384
- }
385
-
386
- // Menus and Navigation Links.
387
- if( menus ){
388
- spinner.text = `Reconstructing navigation menus to ${to.url}`;
389
- await createTaxonomies(menus, toOptions, 'menus', spinner);
390
- await createTaxonomies(menuNavItems, toOptions, 'menu-items', spinner);
391
- }
392
-
393
-
394
- // Settings.
395
- if( settings ){
396
- spinner.text = `Updating site settings to ${to.url}`;
397
- await createTaxonomies(settings, toOptions, 'settings', spinner);
398
- }
399
-
400
- spinner.text = `Sync from ${from.url} to ${to.url} completed successfully.`
401
-
402
- };