@loopress/cli 0.14.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 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.14.0 linux-x64 node-v24.18.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)
@@ -58,9 +62,10 @@ report.
58
62
  * [`lps plugin push`](#lps-plugin-push)
59
63
  * [`lps project config`](#lps-project-config)
60
64
  * [`lps project list`](#lps-project-list)
65
+ * [`lps project pull`](#lps-project-pull)
66
+ * [`lps project push`](#lps-project-push)
61
67
  * [`lps project remove`](#lps-project-remove)
62
68
  * [`lps project switch`](#lps-project-switch)
63
- * [`lps project sync`](#lps-project-sync)
64
69
  * [`lps snippet list`](#lps-snippet-list)
65
70
  * [`lps snippet publish [PATH]`](#lps-snippet-publish-path)
66
71
  * [`lps snippet pull [PATH]`](#lps-snippet-pull-path)
@@ -69,9 +74,109 @@ report.
69
74
  * [`lps telemetry disable`](#lps-telemetry-disable)
70
75
  * [`lps telemetry enable`](#lps-telemetry-enable)
71
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
+
72
177
  ## `lps composer pull`
73
178
 
74
- Pull composer.lock from WordPress
179
+ Pull composer.json and composer.lock from WordPress
75
180
 
76
181
  ```
77
182
  USAGE
@@ -81,7 +186,7 @@ FLAGS
81
186
  -d, --dry-run Show what would change without making changes
82
187
 
83
188
  DESCRIPTION
84
- Pull composer.lock from WordPress
189
+ Pull composer.json and composer.lock from WordPress
85
190
 
86
191
  EXAMPLES
87
192
  $ lps composer pull
@@ -89,7 +194,7 @@ EXAMPLES
89
194
  $ lps composer pull --dry-run
90
195
  ```
91
196
 
92
- _See code: [src/commands/composer/pull.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/composer/pull.ts)_
197
+ _See code: [src/commands/composer/pull.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/composer/pull.ts)_
93
198
 
94
199
  ## `lps composer push`
95
200
 
@@ -111,7 +216,7 @@ EXAMPLES
111
216
  $ lps composer push --dry-run
112
217
  ```
113
218
 
114
- _See code: [src/commands/composer/push.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/composer/push.ts)_
219
+ _See code: [src/commands/composer/push.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/composer/push.ts)_
115
220
 
116
221
  ## `lps help [COMMAND]`
117
222
 
@@ -148,7 +253,7 @@ EXAMPLES
148
253
  $ lps init
149
254
  ```
150
255
 
151
- _See code: [src/commands/init.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/init.ts)_
256
+ _See code: [src/commands/init.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/init.ts)_
152
257
 
153
258
  ## `lps login`
154
259
 
@@ -165,7 +270,7 @@ EXAMPLES
165
270
  $ lps login
166
271
  ```
167
272
 
168
- _See code: [src/commands/login.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/login.ts)_
273
+ _See code: [src/commands/login.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/login.ts)_
169
274
 
170
275
  ## `lps logout`
171
276
 
@@ -182,7 +287,7 @@ EXAMPLES
182
287
  $ lps logout
183
288
  ```
184
289
 
185
- _See code: [src/commands/logout.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/logout.ts)_
290
+ _See code: [src/commands/logout.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/logout.ts)_
186
291
 
187
292
  ## `lps plugin add SLUG`
188
293
 
@@ -207,7 +312,7 @@ EXAMPLES
207
312
  $ lps plugin add contact-form-7 --dry-run
208
313
  ```
209
314
 
210
- _See code: [src/commands/plugin/add.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/plugin/add.ts)_
315
+ _See code: [src/commands/plugin/add.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/plugin/add.ts)_
211
316
 
212
317
  ## `lps plugin pull`
213
318
 
@@ -229,7 +334,7 @@ EXAMPLES
229
334
  $ lps plugin pull --dry-run
230
335
  ```
231
336
 
232
- _See code: [src/commands/plugin/pull.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/plugin/pull.ts)_
337
+ _See code: [src/commands/plugin/pull.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/plugin/pull.ts)_
233
338
 
234
339
  ## `lps plugin push`
235
340
 
@@ -251,7 +356,7 @@ EXAMPLES
251
356
  $ lps plugin push --dry-run
252
357
  ```
253
358
 
254
- _See code: [src/commands/plugin/push.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/plugin/push.ts)_
359
+ _See code: [src/commands/plugin/push.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/plugin/push.ts)_
255
360
 
256
361
  ## `lps project config`
257
362
 
@@ -269,7 +374,7 @@ EXAMPLES
269
374
  $ lps project config
270
375
  ```
271
376
 
272
- _See code: [src/commands/project/config.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/project/config.ts)_
377
+ _See code: [src/commands/project/config.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/project/config.ts)_
273
378
 
274
379
  ## `lps project list`
275
380
 
@@ -286,58 +391,75 @@ EXAMPLES
286
391
  $ lps project list
287
392
  ```
288
393
 
289
- _See code: [src/commands/project/list.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/project/list.ts)_
394
+ _See code: [src/commands/project/list.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/project/list.ts)_
290
395
 
291
- ## `lps project remove`
396
+ ## `lps project pull`
292
397
 
293
- Remove one or more WordPress projects or environments
398
+ Pull projects and environments from your Loopress account that are not configured locally yet
294
399
 
295
400
  ```
296
401
  USAGE
297
- $ lps project remove
402
+ $ lps project pull
298
403
 
299
404
  DESCRIPTION
300
- Remove one or more WordPress projects or environments
405
+ Pull projects and environments from your Loopress account that are not configured locally yet
301
406
 
302
407
  EXAMPLES
303
- $ lps project remove
408
+ $ lps project pull
304
409
  ```
305
410
 
306
- _See code: [src/commands/project/remove.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/project/remove.ts)_
411
+ _See code: [src/commands/project/pull.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/project/pull.ts)_
307
412
 
308
- ## `lps project switch`
413
+ ## `lps project push`
309
414
 
310
- Switch the active project and environment
415
+ Push locally configured projects, environments and credentials to your Loopress account
311
416
 
312
417
  ```
313
418
  USAGE
314
- $ lps project switch
419
+ $ lps project push
315
420
 
316
421
  DESCRIPTION
317
- Switch the active project and environment
422
+ Push locally configured projects, environments and credentials to your Loopress account
318
423
 
319
424
  EXAMPLES
320
- $ lps project switch
425
+ $ lps project push
321
426
  ```
322
427
 
323
- _See code: [src/commands/project/switch.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/project/switch.ts)_
428
+ _See code: [src/commands/project/push.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/project/push.ts)_
324
429
 
325
- ## `lps project sync`
430
+ ## `lps project remove`
326
431
 
327
- Sync locally configured projects and environments with your Loopress account
432
+ Remove one or more WordPress projects or environments
328
433
 
329
434
  ```
330
435
  USAGE
331
- $ lps project sync
436
+ $ lps project remove
332
437
 
333
438
  DESCRIPTION
334
- Sync locally configured projects and environments with your Loopress account
439
+ Remove one or more WordPress projects or environments
335
440
 
336
441
  EXAMPLES
337
- $ lps project sync
442
+ $ lps project remove
443
+ ```
444
+
445
+ _See code: [src/commands/project/remove.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/project/remove.ts)_
446
+
447
+ ## `lps project switch`
448
+
449
+ Switch the active project and environment
450
+
451
+ ```
452
+ USAGE
453
+ $ lps project switch
454
+
455
+ DESCRIPTION
456
+ Switch the active project and environment
457
+
458
+ EXAMPLES
459
+ $ lps project switch
338
460
  ```
339
461
 
340
- _See code: [src/commands/project/sync.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/project/sync.ts)_
462
+ _See code: [src/commands/project/switch.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/project/switch.ts)_
341
463
 
342
464
  ## `lps snippet list`
343
465
 
@@ -357,7 +479,7 @@ EXAMPLES
357
479
  $ lps snippet list
358
480
  ```
359
481
 
360
- _See code: [src/commands/snippet/list.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/snippet/list.ts)_
482
+ _See code: [src/commands/snippet/list.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/snippet/list.ts)_
361
483
 
362
484
  ## `lps snippet publish [PATH]`
363
485
 
@@ -380,7 +502,7 @@ EXAMPLES
380
502
  $ lps snippet publish --path ./snippets
381
503
  ```
382
504
 
383
- _See code: [src/commands/snippet/publish.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/snippet/publish.ts)_
505
+ _See code: [src/commands/snippet/publish.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/snippet/publish.ts)_
384
506
 
385
507
  ## `lps snippet pull [PATH]`
386
508
 
@@ -405,7 +527,7 @@ EXAMPLES
405
527
  $ lps snippet pull --path ./snippets
406
528
  ```
407
529
 
408
- _See code: [src/commands/snippet/pull.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/snippet/pull.ts)_
530
+ _See code: [src/commands/snippet/pull.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/snippet/pull.ts)_
409
531
 
410
532
  ## `lps snippet push [PATH]`
411
533
 
@@ -431,7 +553,7 @@ EXAMPLES
431
553
  $ lps snippet push --path ./snippets
432
554
  ```
433
555
 
434
- _See code: [src/commands/snippet/push.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/snippet/push.ts)_
556
+ _See code: [src/commands/snippet/push.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/snippet/push.ts)_
435
557
 
436
558
  ## `lps status`
437
559
 
@@ -448,7 +570,7 @@ EXAMPLES
448
570
  $ lps status
449
571
  ```
450
572
 
451
- _See code: [src/commands/status.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/status.ts)_
573
+ _See code: [src/commands/status.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/status.ts)_
452
574
 
453
575
  ## `lps telemetry disable`
454
576
 
@@ -465,7 +587,7 @@ EXAMPLES
465
587
  $ lps telemetry disable
466
588
  ```
467
589
 
468
- _See code: [src/commands/telemetry/disable.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/telemetry/disable.ts)_
590
+ _See code: [src/commands/telemetry/disable.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/telemetry/disable.ts)_
469
591
 
470
592
  ## `lps telemetry enable`
471
593
 
@@ -482,5 +604,5 @@ EXAMPLES
482
604
  $ lps telemetry enable
483
605
  ```
484
606
 
485
- _See code: [src/commands/telemetry/enable.ts](https://github.com/loopress/loopress/blob/v0.14.0/src/commands/telemetry/enable.ts)_
607
+ _See code: [src/commands/telemetry/enable.ts](https://github.com/loopress/loopress/blob/v0.16.0/src/commands/telemetry/enable.ts)_
486
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
+ }