@loopress/cli 0.15.0 → 0.16.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/README.md +128 -24
- package/dist/commands/acf/list.d.ts +10 -0
- package/dist/commands/acf/list.js +36 -0
- package/dist/commands/acf/pull.d.ts +15 -0
- package/dist/commands/acf/pull.js +80 -0
- package/dist/commands/acf/push.d.ts +17 -0
- package/dist/commands/acf/push.js +108 -0
- package/dist/commands/composer/init.d.ts +9 -0
- package/dist/commands/composer/init.js +47 -0
- package/dist/commands/composer/pull.js +7 -4
- package/dist/help.js +1 -1
- package/dist/lib/base.d.ts +1 -0
- package/dist/lib/base.js +5 -0
- package/dist/types/project-config.generated.d.ts +4 -0
- package/dist/utils/acf-format.d.ts +4 -0
- package/dist/utils/acf-format.js +12 -0
- package/dist/utils/composer.d.ts +8 -0
- package/dist/utils/plugins.js +5 -3
- package/oclif.manifest.json +310 -106
- package/package.json +4 -1
- package/schemas/project-config.schema.json +5 -0
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ $ npm install -g @loopress/cli
|
|
|
21
21
|
$ lps COMMAND
|
|
22
22
|
running command...
|
|
23
23
|
$ lps (--version)
|
|
24
|
-
@loopress/cli/0.
|
|
24
|
+
@loopress/cli/0.16.0 linux-x64 node-v24.18.0
|
|
25
25
|
$ lps --help [COMMAND]
|
|
26
26
|
USAGE
|
|
27
27
|
$ lps COMMAND
|
|
@@ -47,6 +47,10 @@ report.
|
|
|
47
47
|
# Commands
|
|
48
48
|
|
|
49
49
|
<!-- commands -->
|
|
50
|
+
* [`lps acf list`](#lps-acf-list)
|
|
51
|
+
* [`lps acf pull [PATH]`](#lps-acf-pull-path)
|
|
52
|
+
* [`lps acf push [PATH]`](#lps-acf-push-path)
|
|
53
|
+
* [`lps composer init`](#lps-composer-init)
|
|
50
54
|
* [`lps composer pull`](#lps-composer-pull)
|
|
51
55
|
* [`lps composer push`](#lps-composer-push)
|
|
52
56
|
* [`lps help [COMMAND]`](#lps-help-command)
|
|
@@ -70,9 +74,109 @@ report.
|
|
|
70
74
|
* [`lps telemetry disable`](#lps-telemetry-disable)
|
|
71
75
|
* [`lps telemetry enable`](#lps-telemetry-enable)
|
|
72
76
|
|
|
77
|
+
## `lps acf list`
|
|
78
|
+
|
|
79
|
+
List ACF field groups, post types, taxonomies, and options pages from WordPress
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
USAGE
|
|
83
|
+
$ lps acf list [-j] [--type field-groups|post-types|taxonomies|options-pages...]
|
|
84
|
+
|
|
85
|
+
FLAGS
|
|
86
|
+
-j, --json Output in JSON format
|
|
87
|
+
--type=<option>... Limit to specific ACF object types
|
|
88
|
+
<options: field-groups|post-types|taxonomies|options-pages>
|
|
89
|
+
|
|
90
|
+
DESCRIPTION
|
|
91
|
+
List ACF field groups, post types, taxonomies, and options pages from WordPress
|
|
92
|
+
|
|
93
|
+
EXAMPLES
|
|
94
|
+
$ lps acf list
|
|
95
|
+
|
|
96
|
+
$ lps acf list --type field-groups
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
_See code: [src/commands/acf/list.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/acf/list.ts)_
|
|
100
|
+
|
|
101
|
+
## `lps acf pull [PATH]`
|
|
102
|
+
|
|
103
|
+
Pull ACF field groups, post types, taxonomies, and options pages from WordPress
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
USAGE
|
|
107
|
+
$ lps acf pull [PATH] [-d] [--type field-groups|post-types|taxonomies|options-pages...]
|
|
108
|
+
|
|
109
|
+
ARGUMENTS
|
|
110
|
+
[PATH] Path to ACF directory (overrides project config)
|
|
111
|
+
|
|
112
|
+
FLAGS
|
|
113
|
+
-d, --dry-run Show what would change without making changes
|
|
114
|
+
--type=<option>... Limit to specific ACF object types
|
|
115
|
+
<options: field-groups|post-types|taxonomies|options-pages>
|
|
116
|
+
|
|
117
|
+
DESCRIPTION
|
|
118
|
+
Pull ACF field groups, post types, taxonomies, and options pages from WordPress
|
|
119
|
+
|
|
120
|
+
EXAMPLES
|
|
121
|
+
$ lps acf pull
|
|
122
|
+
|
|
123
|
+
$ lps acf pull --type field-groups
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
_See code: [src/commands/acf/pull.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/acf/pull.ts)_
|
|
127
|
+
|
|
128
|
+
## `lps acf push [PATH]`
|
|
129
|
+
|
|
130
|
+
Push ACF field groups, post types, taxonomies, and options pages to WordPress
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
USAGE
|
|
134
|
+
$ lps acf push [PATH] [-d] [--type field-groups|post-types|taxonomies|options-pages...]
|
|
135
|
+
|
|
136
|
+
ARGUMENTS
|
|
137
|
+
[PATH] Path to ACF directory (overrides project config)
|
|
138
|
+
|
|
139
|
+
FLAGS
|
|
140
|
+
-d, --dry-run Show what would change without making changes
|
|
141
|
+
--type=<option>... Limit to specific ACF object types
|
|
142
|
+
<options: field-groups|post-types|taxonomies|options-pages>
|
|
143
|
+
|
|
144
|
+
DESCRIPTION
|
|
145
|
+
Push ACF field groups, post types, taxonomies, and options pages to WordPress
|
|
146
|
+
|
|
147
|
+
EXAMPLES
|
|
148
|
+
$ lps acf push
|
|
149
|
+
|
|
150
|
+
$ lps acf push --type field-groups
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
_See code: [src/commands/acf/push.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/acf/push.ts)_
|
|
154
|
+
|
|
155
|
+
## `lps composer init`
|
|
156
|
+
|
|
157
|
+
Create a composer.json wired to WPackagist for installing WordPress.org plugins and themes
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
USAGE
|
|
161
|
+
$ lps composer init [-d]
|
|
162
|
+
|
|
163
|
+
FLAGS
|
|
164
|
+
-d, --dry-run Show what would change without making changes
|
|
165
|
+
|
|
166
|
+
DESCRIPTION
|
|
167
|
+
Create a composer.json wired to WPackagist for installing WordPress.org plugins and themes
|
|
168
|
+
|
|
169
|
+
EXAMPLES
|
|
170
|
+
$ lps composer init
|
|
171
|
+
|
|
172
|
+
$ lps composer init --dry-run
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
_See code: [src/commands/composer/init.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/composer/init.ts)_
|
|
176
|
+
|
|
73
177
|
## `lps composer pull`
|
|
74
178
|
|
|
75
|
-
Pull composer.lock from WordPress
|
|
179
|
+
Pull composer.json and composer.lock from WordPress
|
|
76
180
|
|
|
77
181
|
```
|
|
78
182
|
USAGE
|
|
@@ -82,7 +186,7 @@ FLAGS
|
|
|
82
186
|
-d, --dry-run Show what would change without making changes
|
|
83
187
|
|
|
84
188
|
DESCRIPTION
|
|
85
|
-
Pull composer.lock from WordPress
|
|
189
|
+
Pull composer.json and composer.lock from WordPress
|
|
86
190
|
|
|
87
191
|
EXAMPLES
|
|
88
192
|
$ lps composer pull
|
|
@@ -90,7 +194,7 @@ EXAMPLES
|
|
|
90
194
|
$ lps composer pull --dry-run
|
|
91
195
|
```
|
|
92
196
|
|
|
93
|
-
_See code: [src/commands/composer/pull.ts](https://github.com/loopress/loopress/blob/v0.
|
|
197
|
+
_See code: [src/commands/composer/pull.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/composer/pull.ts)_
|
|
94
198
|
|
|
95
199
|
## `lps composer push`
|
|
96
200
|
|
|
@@ -112,7 +216,7 @@ EXAMPLES
|
|
|
112
216
|
$ lps composer push --dry-run
|
|
113
217
|
```
|
|
114
218
|
|
|
115
|
-
_See code: [src/commands/composer/push.ts](https://github.com/loopress/loopress/blob/v0.
|
|
219
|
+
_See code: [src/commands/composer/push.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/composer/push.ts)_
|
|
116
220
|
|
|
117
221
|
## `lps help [COMMAND]`
|
|
118
222
|
|
|
@@ -149,7 +253,7 @@ EXAMPLES
|
|
|
149
253
|
$ lps init
|
|
150
254
|
```
|
|
151
255
|
|
|
152
|
-
_See code: [src/commands/init.ts](https://github.com/loopress/loopress/blob/v0.
|
|
256
|
+
_See code: [src/commands/init.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/init.ts)_
|
|
153
257
|
|
|
154
258
|
## `lps login`
|
|
155
259
|
|
|
@@ -166,7 +270,7 @@ EXAMPLES
|
|
|
166
270
|
$ lps login
|
|
167
271
|
```
|
|
168
272
|
|
|
169
|
-
_See code: [src/commands/login.ts](https://github.com/loopress/loopress/blob/v0.
|
|
273
|
+
_See code: [src/commands/login.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/login.ts)_
|
|
170
274
|
|
|
171
275
|
## `lps logout`
|
|
172
276
|
|
|
@@ -183,7 +287,7 @@ EXAMPLES
|
|
|
183
287
|
$ lps logout
|
|
184
288
|
```
|
|
185
289
|
|
|
186
|
-
_See code: [src/commands/logout.ts](https://github.com/loopress/loopress/blob/v0.
|
|
290
|
+
_See code: [src/commands/logout.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/logout.ts)_
|
|
187
291
|
|
|
188
292
|
## `lps plugin add SLUG`
|
|
189
293
|
|
|
@@ -208,7 +312,7 @@ EXAMPLES
|
|
|
208
312
|
$ lps plugin add contact-form-7 --dry-run
|
|
209
313
|
```
|
|
210
314
|
|
|
211
|
-
_See code: [src/commands/plugin/add.ts](https://github.com/loopress/loopress/blob/v0.
|
|
315
|
+
_See code: [src/commands/plugin/add.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/plugin/add.ts)_
|
|
212
316
|
|
|
213
317
|
## `lps plugin pull`
|
|
214
318
|
|
|
@@ -230,7 +334,7 @@ EXAMPLES
|
|
|
230
334
|
$ lps plugin pull --dry-run
|
|
231
335
|
```
|
|
232
336
|
|
|
233
|
-
_See code: [src/commands/plugin/pull.ts](https://github.com/loopress/loopress/blob/v0.
|
|
337
|
+
_See code: [src/commands/plugin/pull.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/plugin/pull.ts)_
|
|
234
338
|
|
|
235
339
|
## `lps plugin push`
|
|
236
340
|
|
|
@@ -252,7 +356,7 @@ EXAMPLES
|
|
|
252
356
|
$ lps plugin push --dry-run
|
|
253
357
|
```
|
|
254
358
|
|
|
255
|
-
_See code: [src/commands/plugin/push.ts](https://github.com/loopress/loopress/blob/v0.
|
|
359
|
+
_See code: [src/commands/plugin/push.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/plugin/push.ts)_
|
|
256
360
|
|
|
257
361
|
## `lps project config`
|
|
258
362
|
|
|
@@ -270,7 +374,7 @@ EXAMPLES
|
|
|
270
374
|
$ lps project config
|
|
271
375
|
```
|
|
272
376
|
|
|
273
|
-
_See code: [src/commands/project/config.ts](https://github.com/loopress/loopress/blob/v0.
|
|
377
|
+
_See code: [src/commands/project/config.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/project/config.ts)_
|
|
274
378
|
|
|
275
379
|
## `lps project list`
|
|
276
380
|
|
|
@@ -287,7 +391,7 @@ EXAMPLES
|
|
|
287
391
|
$ lps project list
|
|
288
392
|
```
|
|
289
393
|
|
|
290
|
-
_See code: [src/commands/project/list.ts](https://github.com/loopress/loopress/blob/v0.
|
|
394
|
+
_See code: [src/commands/project/list.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/project/list.ts)_
|
|
291
395
|
|
|
292
396
|
## `lps project pull`
|
|
293
397
|
|
|
@@ -304,7 +408,7 @@ EXAMPLES
|
|
|
304
408
|
$ lps project pull
|
|
305
409
|
```
|
|
306
410
|
|
|
307
|
-
_See code: [src/commands/project/pull.ts](https://github.com/loopress/loopress/blob/v0.
|
|
411
|
+
_See code: [src/commands/project/pull.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/project/pull.ts)_
|
|
308
412
|
|
|
309
413
|
## `lps project push`
|
|
310
414
|
|
|
@@ -321,7 +425,7 @@ EXAMPLES
|
|
|
321
425
|
$ lps project push
|
|
322
426
|
```
|
|
323
427
|
|
|
324
|
-
_See code: [src/commands/project/push.ts](https://github.com/loopress/loopress/blob/v0.
|
|
428
|
+
_See code: [src/commands/project/push.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/project/push.ts)_
|
|
325
429
|
|
|
326
430
|
## `lps project remove`
|
|
327
431
|
|
|
@@ -338,7 +442,7 @@ EXAMPLES
|
|
|
338
442
|
$ lps project remove
|
|
339
443
|
```
|
|
340
444
|
|
|
341
|
-
_See code: [src/commands/project/remove.ts](https://github.com/loopress/loopress/blob/v0.
|
|
445
|
+
_See code: [src/commands/project/remove.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/project/remove.ts)_
|
|
342
446
|
|
|
343
447
|
## `lps project switch`
|
|
344
448
|
|
|
@@ -355,7 +459,7 @@ EXAMPLES
|
|
|
355
459
|
$ lps project switch
|
|
356
460
|
```
|
|
357
461
|
|
|
358
|
-
_See code: [src/commands/project/switch.ts](https://github.com/loopress/loopress/blob/v0.
|
|
462
|
+
_See code: [src/commands/project/switch.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/project/switch.ts)_
|
|
359
463
|
|
|
360
464
|
## `lps snippet list`
|
|
361
465
|
|
|
@@ -375,7 +479,7 @@ EXAMPLES
|
|
|
375
479
|
$ lps snippet list
|
|
376
480
|
```
|
|
377
481
|
|
|
378
|
-
_See code: [src/commands/snippet/list.ts](https://github.com/loopress/loopress/blob/v0.
|
|
482
|
+
_See code: [src/commands/snippet/list.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/snippet/list.ts)_
|
|
379
483
|
|
|
380
484
|
## `lps snippet publish [PATH]`
|
|
381
485
|
|
|
@@ -398,7 +502,7 @@ EXAMPLES
|
|
|
398
502
|
$ lps snippet publish --path ./snippets
|
|
399
503
|
```
|
|
400
504
|
|
|
401
|
-
_See code: [src/commands/snippet/publish.ts](https://github.com/loopress/loopress/blob/v0.
|
|
505
|
+
_See code: [src/commands/snippet/publish.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/snippet/publish.ts)_
|
|
402
506
|
|
|
403
507
|
## `lps snippet pull [PATH]`
|
|
404
508
|
|
|
@@ -423,7 +527,7 @@ EXAMPLES
|
|
|
423
527
|
$ lps snippet pull --path ./snippets
|
|
424
528
|
```
|
|
425
529
|
|
|
426
|
-
_See code: [src/commands/snippet/pull.ts](https://github.com/loopress/loopress/blob/v0.
|
|
530
|
+
_See code: [src/commands/snippet/pull.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/snippet/pull.ts)_
|
|
427
531
|
|
|
428
532
|
## `lps snippet push [PATH]`
|
|
429
533
|
|
|
@@ -449,7 +553,7 @@ EXAMPLES
|
|
|
449
553
|
$ lps snippet push --path ./snippets
|
|
450
554
|
```
|
|
451
555
|
|
|
452
|
-
_See code: [src/commands/snippet/push.ts](https://github.com/loopress/loopress/blob/v0.
|
|
556
|
+
_See code: [src/commands/snippet/push.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/snippet/push.ts)_
|
|
453
557
|
|
|
454
558
|
## `lps status`
|
|
455
559
|
|
|
@@ -466,7 +570,7 @@ EXAMPLES
|
|
|
466
570
|
$ lps status
|
|
467
571
|
```
|
|
468
572
|
|
|
469
|
-
_See code: [src/commands/status.ts](https://github.com/loopress/loopress/blob/v0.
|
|
573
|
+
_See code: [src/commands/status.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/status.ts)_
|
|
470
574
|
|
|
471
575
|
## `lps telemetry disable`
|
|
472
576
|
|
|
@@ -483,7 +587,7 @@ EXAMPLES
|
|
|
483
587
|
$ lps telemetry disable
|
|
484
588
|
```
|
|
485
589
|
|
|
486
|
-
_See code: [src/commands/telemetry/disable.ts](https://github.com/loopress/loopress/blob/v0.
|
|
590
|
+
_See code: [src/commands/telemetry/disable.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/telemetry/disable.ts)_
|
|
487
591
|
|
|
488
592
|
## `lps telemetry enable`
|
|
489
593
|
|
|
@@ -500,5 +604,5 @@ EXAMPLES
|
|
|
500
604
|
$ lps telemetry enable
|
|
501
605
|
```
|
|
502
606
|
|
|
503
|
-
_See code: [src/commands/telemetry/enable.ts](https://github.com/loopress/loopress/blob/v0.
|
|
607
|
+
_See code: [src/commands/telemetry/enable.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/telemetry/enable.ts)_
|
|
504
608
|
<!-- commandsstop -->
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
export default class List extends LoopressCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
type: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { LoopressCommand } from '../../lib/base.js';
|
|
3
|
+
import { ACF_OBJECT_TYPES, acfEndpoint, getAcfKey } from '../../utils/acf-format.js';
|
|
4
|
+
export default class List extends LoopressCommand {
|
|
5
|
+
static description = 'List ACF field groups, post types, taxonomies, and options pages from WordPress';
|
|
6
|
+
static examples = ['$ lps acf list', '$ lps acf list --type field-groups'];
|
|
7
|
+
static flags = {
|
|
8
|
+
json: Flags.boolean({ char: 'j', description: 'Output in JSON format' }),
|
|
9
|
+
type: Flags.string({ description: 'Limit to specific ACF object types', multiple: true, options: ACF_OBJECT_TYPES }),
|
|
10
|
+
};
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(List);
|
|
13
|
+
const types = (flags.type && flags.type.length > 0 ? flags.type : ACF_OBJECT_TYPES);
|
|
14
|
+
const byType = {};
|
|
15
|
+
for (const type of types) {
|
|
16
|
+
byType[type] = await this.wp.get(acfEndpoint(type));
|
|
17
|
+
}
|
|
18
|
+
if (flags.json) {
|
|
19
|
+
this.log(JSON.stringify(byType, null, 2));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
for (const type of types) {
|
|
23
|
+
const objects = byType[type];
|
|
24
|
+
this.log(`${type} (${objects.length}):`);
|
|
25
|
+
if (objects.length === 0) {
|
|
26
|
+
this.log(' (none)');
|
|
27
|
+
this.log('');
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
for (const object of objects) {
|
|
31
|
+
this.log(` ${getAcfKey(object) ?? '(no key)'}. ${String(object.title ?? '(untitled)')}`);
|
|
32
|
+
}
|
|
33
|
+
this.log('');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
export default class Pull extends LoopressCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
type: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
private findOrphanedFiles;
|
|
14
|
+
private pullType;
|
|
15
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { Listr } from 'listr2';
|
|
3
|
+
import { mkdir, readdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { extname, join } from 'node:path';
|
|
5
|
+
import { LoopressCommand } from '../../lib/base.js';
|
|
6
|
+
import { ACF_OBJECT_TYPES, acfEndpoint, getAcfKey } from '../../utils/acf-format.js';
|
|
7
|
+
export default class Pull extends LoopressCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
path: Args.string({ description: 'Path to ACF directory (overrides project config)' }),
|
|
10
|
+
};
|
|
11
|
+
static description = 'Pull ACF field groups, post types, taxonomies, and options pages from WordPress';
|
|
12
|
+
static examples = ['$ lps acf pull', '$ lps acf pull --type field-groups'];
|
|
13
|
+
static flags = {
|
|
14
|
+
...LoopressCommand.dryRunFlag,
|
|
15
|
+
type: Flags.string({ description: 'Limit to specific ACF object types', multiple: true, options: ACF_OBJECT_TYPES }),
|
|
16
|
+
};
|
|
17
|
+
async run() {
|
|
18
|
+
const { args, flags } = await this.parse(Pull);
|
|
19
|
+
const { url } = this.siteConfig;
|
|
20
|
+
const path = this.resolveAcfPath(args.path);
|
|
21
|
+
const types = (flags.type && flags.type.length > 0 ? flags.type : ACF_OBJECT_TYPES);
|
|
22
|
+
this.log(`Pulling ACF configuration from ${url}`);
|
|
23
|
+
this.log(`ACF path: ${path}`);
|
|
24
|
+
for (const type of types) {
|
|
25
|
+
await this.pullType(type, path);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Every file in a type's subdirectory is unambiguously `<key>.json` — unlike snippets, ACF
|
|
29
|
+
// objects have no numeric-id/slug filename convention. `key` is the stable identity ACF
|
|
30
|
+
// itself already uses for this (see its own Local JSON mechanism), so orphan detection is a
|
|
31
|
+
// plain set difference, no regex/extension juggling needed.
|
|
32
|
+
async findOrphanedFiles(dir, keepKeys) {
|
|
33
|
+
let files;
|
|
34
|
+
try {
|
|
35
|
+
files = await readdir(dir);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error.code === 'ENOENT')
|
|
39
|
+
return [];
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
return files.filter((file) => extname(file) === '.json' && !keepKeys.has(file.slice(0, -'.json'.length)));
|
|
43
|
+
}
|
|
44
|
+
async pullType(type, basePath) {
|
|
45
|
+
const dir = join(basePath, type);
|
|
46
|
+
const remoteList = await this.wp.get(acfEndpoint(type));
|
|
47
|
+
const withKey = remoteList.filter((object) => getAcfKey(object) !== null);
|
|
48
|
+
const skipped = remoteList.length - withKey.length;
|
|
49
|
+
const orphans = await this.findOrphanedFiles(dir, new Set(withKey.map((object) => getAcfKey(object))));
|
|
50
|
+
if (this.dryRun) {
|
|
51
|
+
this.log(`[dry-run] Would pull ${withKey.length} ${type} to ${dir}`);
|
|
52
|
+
if (orphans.length > 0) {
|
|
53
|
+
this.log(`[dry-run] Would remove ${orphans.length} local file${orphans.length === 1 ? '' : 's'} in ${dir} no longer present on WordPress: ${orphans.join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (withKey.length > 0)
|
|
58
|
+
await mkdir(dir, { recursive: true });
|
|
59
|
+
await new Listr(withKey.map((object) => {
|
|
60
|
+
const key = getAcfKey(object);
|
|
61
|
+
return {
|
|
62
|
+
async task(_ctx, task) {
|
|
63
|
+
await writeFile(join(dir, `${key}.json`), JSON.stringify(object, null, 2) + '\n');
|
|
64
|
+
task.output = `Pulled: ${key}`;
|
|
65
|
+
},
|
|
66
|
+
title: `Pull ${key}`,
|
|
67
|
+
};
|
|
68
|
+
})).run();
|
|
69
|
+
for (const file of orphans) {
|
|
70
|
+
await rm(join(dir, file), { force: true });
|
|
71
|
+
}
|
|
72
|
+
if (orphans.length > 0) {
|
|
73
|
+
this.warn(`Removed ${orphans.length} local file${orphans.length === 1 ? '' : 's'} in ${dir} no longer present on WordPress: ${orphans.join(', ')}`);
|
|
74
|
+
}
|
|
75
|
+
this.log(`Pulled ${withKey.length} ${type} to ${dir}`);
|
|
76
|
+
if (skipped > 0) {
|
|
77
|
+
this.warn(`${skipped} ${type} skipped because they have no key`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PushCommand } from '../../lib/push-command.js';
|
|
2
|
+
export default class Push extends PushCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
type: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
private failedCount;
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
private loadObjects;
|
|
15
|
+
private pushObject;
|
|
16
|
+
private pushType;
|
|
17
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { Listr } from 'listr2';
|
|
3
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
4
|
+
import { extname, join } from 'node:path';
|
|
5
|
+
import { PushCommand } from '../../lib/push-command.js';
|
|
6
|
+
import { ACF_OBJECT_TYPES, acfEndpoint, getAcfKey } from '../../utils/acf-format.js';
|
|
7
|
+
export default class Push extends PushCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
path: Args.string({ description: 'Path to ACF directory (overrides project config)' }),
|
|
10
|
+
};
|
|
11
|
+
static description = 'Push ACF field groups, post types, taxonomies, and options pages to WordPress';
|
|
12
|
+
static examples = ['$ lps acf push', '$ lps acf push --type field-groups'];
|
|
13
|
+
static flags = {
|
|
14
|
+
...PushCommand.dryRunFlag,
|
|
15
|
+
type: Flags.string({ description: 'Limit to specific ACF object types', multiple: true, options: ACF_OBJECT_TYPES }),
|
|
16
|
+
};
|
|
17
|
+
failedCount = 0;
|
|
18
|
+
async run() {
|
|
19
|
+
const { args, flags } = await this.parse(Push);
|
|
20
|
+
const { url } = this.siteConfig;
|
|
21
|
+
const path = this.resolveAcfPath(args.path);
|
|
22
|
+
const types = (flags.type && flags.type.length > 0 ? flags.type : ACF_OBJECT_TYPES);
|
|
23
|
+
this.log(`Pushing ACF configuration to ${url}`);
|
|
24
|
+
this.log(`ACF path: ${path}`);
|
|
25
|
+
for (const type of types) {
|
|
26
|
+
await this.pushType(type, path);
|
|
27
|
+
}
|
|
28
|
+
if (this.failedCount > 0) {
|
|
29
|
+
this.error(`${this.failedCount} ACF object${this.failedCount === 1 ? '' : 's'} failed to push.`);
|
|
30
|
+
}
|
|
31
|
+
if (this.dryRun)
|
|
32
|
+
return;
|
|
33
|
+
await this.recordSuccess();
|
|
34
|
+
this.log('All ACF objects pushed.');
|
|
35
|
+
}
|
|
36
|
+
// One object's file is read in isolation: a corrupted or hand-broken JSON file (or one
|
|
37
|
+
// missing/emptying its "key") must only skip that object, not abort loading the rest of
|
|
38
|
+
// the type's directory — same principle as loadSnippets() for the snippet feature.
|
|
39
|
+
async loadObjects(dir) {
|
|
40
|
+
let files;
|
|
41
|
+
try {
|
|
42
|
+
files = await readdir(dir);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error.code === 'ENOENT')
|
|
46
|
+
return [];
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
const objects = [];
|
|
50
|
+
for (const file of files) {
|
|
51
|
+
if (extname(file) !== '.json')
|
|
52
|
+
continue;
|
|
53
|
+
const filePath = join(dir, file);
|
|
54
|
+
let parsed;
|
|
55
|
+
try {
|
|
56
|
+
parsed = JSON.parse(await readFile(filePath, 'utf8'));
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
this.warn(`Skipping "${filePath}": ${error.message}`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (typeof parsed !== 'object' || parsed === null || getAcfKey(parsed) === null) {
|
|
63
|
+
this.warn(`Skipping "${filePath}": missing or invalid "key"`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
objects.push(parsed);
|
|
67
|
+
}
|
|
68
|
+
return objects;
|
|
69
|
+
}
|
|
70
|
+
// Throwing on failure (rather than returning a boolean) is what lets Listr mark the task as
|
|
71
|
+
// failed (red cross) instead of completed; `exitOnError: false` on the task list still lets
|
|
72
|
+
// sibling objects push regardless. POST alone covers create-or-update (the controller resolves
|
|
73
|
+
// that server-side via the object's `key`), unlike snippet push there's no numeric-id PUT/404
|
|
74
|
+
// fallback dance, and no rename-on-push step since `key` is permanently stable.
|
|
75
|
+
async pushObject(type, object, task) {
|
|
76
|
+
const key = getAcfKey(object) ?? '(unknown)';
|
|
77
|
+
if (this.dryRun) {
|
|
78
|
+
if (task)
|
|
79
|
+
task.output = `[dry-run] Would push: ${key}`;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
await this.wp.post(acfEndpoint(type), object);
|
|
84
|
+
if (task)
|
|
85
|
+
task.output = `Pushed: ${key}`;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const message = `Failed to push ${key}: ${error.message}`;
|
|
89
|
+
if (task)
|
|
90
|
+
task.output = message;
|
|
91
|
+
else
|
|
92
|
+
this.warn(` ${message}`);
|
|
93
|
+
this.failedCount++;
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async pushType(type, basePath) {
|
|
98
|
+
const dir = join(basePath, type);
|
|
99
|
+
const objects = await this.loadObjects(dir);
|
|
100
|
+
if (objects.length === 0)
|
|
101
|
+
return;
|
|
102
|
+
this.log(`Found ${objects.length} ${type} to push`);
|
|
103
|
+
await new Listr(objects.map((object) => ({
|
|
104
|
+
task: async (_ctx, task) => this.pushObject(type, object, task),
|
|
105
|
+
title: `Push ${getAcfKey(object) ?? '(unknown)'}`,
|
|
106
|
+
})), { concurrent: false, exitOnError: false }).run();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
export default class ComposerInit extends LoopressCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { confirm } from '@inquirer/prompts';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { writeFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { LoopressCommand } from '../../lib/base.js';
|
|
6
|
+
const WPACKAGIST_REPOSITORY = { type: 'composer', url: 'https://wpackagist.org' };
|
|
7
|
+
const INSTALLERS_PACKAGE = 'composer/installers';
|
|
8
|
+
const INSTALLERS_CONSTRAINT = '^2.0';
|
|
9
|
+
// The server runs Composer with `--working-dir` set to wp-content/loopress/ (see
|
|
10
|
+
// LoopressEnvironment::getDxDir), not the WordPress root, so installer-paths must climb out
|
|
11
|
+
// of that directory to land plugins/themes in their usual wp-content/ locations.
|
|
12
|
+
const INSTALLER_PATHS = {
|
|
13
|
+
'../plugins/{$name}/': ['type:wordpress-plugin'],
|
|
14
|
+
'../themes/{$name}/': ['type:wordpress-theme'],
|
|
15
|
+
};
|
|
16
|
+
export default class ComposerInit extends LoopressCommand {
|
|
17
|
+
static description = 'Create a composer.json wired to WPackagist for installing WordPress.org plugins and themes';
|
|
18
|
+
static examples = ['$ lps composer init', '$ lps composer init --dry-run'];
|
|
19
|
+
static flags = {
|
|
20
|
+
...LoopressCommand.dryRunFlag,
|
|
21
|
+
};
|
|
22
|
+
async run() {
|
|
23
|
+
const composerJsonPath = join(process.cwd(), this.rootDir, 'composer.json');
|
|
24
|
+
if (existsSync(composerJsonPath)) {
|
|
25
|
+
const overwrite = await confirm({ default: false, message: 'composer.json already exists. Overwrite?' });
|
|
26
|
+
if (!overwrite) {
|
|
27
|
+
this.log('Aborted.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const composerJson = {
|
|
32
|
+
extra: { 'installer-paths': INSTALLER_PATHS },
|
|
33
|
+
name: 'loopress/site-dependencies',
|
|
34
|
+
repositories: [WPACKAGIST_REPOSITORY],
|
|
35
|
+
require: {
|
|
36
|
+
[INSTALLERS_PACKAGE]: INSTALLERS_CONSTRAINT,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
if (this.dryRun) {
|
|
40
|
+
this.log(`[dry-run] Would write composer.json to ${composerJsonPath}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
await writeFile(composerJsonPath, JSON.stringify(composerJson, null, 2) + '\n', 'utf8');
|
|
44
|
+
this.log(`Wrote composer.json to ${composerJsonPath}`);
|
|
45
|
+
this.log('Add plugins with `wpackagist-plugin/<slug>` and a theme with `wpackagist-theme/<slug>` in require, then run `lps composer push`.');
|
|
46
|
+
}
|
|
47
|
+
}
|