@loopress/cli 0.5.0 → 0.6.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
@@ -20,7 +20,7 @@ $ npm install -g @loopress/cli
20
20
  $ lps COMMAND
21
21
  running command...
22
22
  $ lps (--version)
23
- @loopress/cli/0.5.0 linux-x64 node-v24.17.0
23
+ @loopress/cli/0.6.0 linux-x64 node-v24.17.0
24
24
  $ lps --help [COMMAND]
25
25
  USAGE
26
26
  $ lps COMMAND
@@ -31,13 +31,15 @@ USAGE
31
31
  # Commands
32
32
 
33
33
  <!-- commands -->
34
+ * [`lps composer pull`](#lps-composer-pull)
35
+ * [`lps composer push`](#lps-composer-push)
34
36
  * [`lps help [COMMAND]`](#lps-help-command)
35
37
  * [`lps init`](#lps-init)
36
38
  * [`lps login`](#lps-login)
37
39
  * [`lps logout`](#lps-logout)
40
+ * [`lps plugin add SLUG [VERSION]`](#lps-plugin-add-slug-version)
38
41
  * [`lps plugin pull`](#lps-plugin-pull)
39
42
  * [`lps plugin push`](#lps-plugin-push)
40
- * [`lps plugin require SLUG [VERSION]`](#lps-plugin-require-slug-version)
41
43
  * [`lps project config`](#lps-project-config)
42
44
  * [`lps project list`](#lps-project-list)
43
45
  * [`lps project remove`](#lps-project-remove)
@@ -48,6 +50,60 @@ USAGE
48
50
  * [`lps snippet pull [PATH]`](#lps-snippet-pull-path)
49
51
  * [`lps snippet push [PATH]`](#lps-snippet-push-path)
50
52
 
53
+ ## `lps composer pull`
54
+
55
+ Pull composer.lock from the WordPress server
56
+
57
+ ```
58
+ USAGE
59
+ $ lps composer pull [--password <value>] [--url <value>] [--user <value>] [-d]
60
+
61
+ FLAGS
62
+ -d, --dry-run Show what would be written without making changes
63
+
64
+ GLOBAL FLAGS
65
+ --password=<value> WordPress application password (fallback; prefer `lps project config`)
66
+ --url=<value> WordPress URL (fallback; prefer `lps project config`)
67
+ --user=<value> WordPress username (fallback; prefer `lps project config`)
68
+
69
+ DESCRIPTION
70
+ Pull composer.lock from the WordPress server
71
+
72
+ EXAMPLES
73
+ $ lps composer pull
74
+
75
+ $ lps composer pull --dry-run
76
+ ```
77
+
78
+ _See code: [src/commands/composer/pull.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/composer/pull.ts)_
79
+
80
+ ## `lps composer push`
81
+
82
+ Upload composer.json and composer.lock to WordPress and run composer install
83
+
84
+ ```
85
+ USAGE
86
+ $ lps composer push [--password <value>] [--url <value>] [--user <value>] [-d]
87
+
88
+ FLAGS
89
+ -d, --dry-run Show what would change without making changes
90
+
91
+ GLOBAL FLAGS
92
+ --password=<value> WordPress application password (fallback; prefer `lps project config`)
93
+ --url=<value> WordPress URL (fallback; prefer `lps project config`)
94
+ --user=<value> WordPress username (fallback; prefer `lps project config`)
95
+
96
+ DESCRIPTION
97
+ Upload composer.json and composer.lock to WordPress and run composer install
98
+
99
+ EXAMPLES
100
+ $ lps composer push
101
+
102
+ $ lps composer push --dry-run
103
+ ```
104
+
105
+ _See code: [src/commands/composer/push.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/composer/push.ts)_
106
+
51
107
  ## `lps help [COMMAND]`
52
108
 
53
109
  Display help for lps.
@@ -66,7 +122,7 @@ DESCRIPTION
66
122
  Display help for lps.
67
123
  ```
68
124
 
69
- _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/6.2.52/src/commands/help.ts)_
125
+ _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/6.2.53/src/commands/help.ts)_
70
126
 
71
127
  ## `lps init`
72
128
 
@@ -83,7 +139,7 @@ EXAMPLES
83
139
  $ lps init
84
140
  ```
85
141
 
86
- _See code: [src/commands/init.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/init.ts)_
142
+ _See code: [src/commands/init.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/init.ts)_
87
143
 
88
144
  ## `lps login`
89
145
 
@@ -100,7 +156,7 @@ EXAMPLES
100
156
  $ lps login
101
157
  ```
102
158
 
103
- _See code: [src/commands/login.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/login.ts)_
159
+ _See code: [src/commands/login.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/login.ts)_
104
160
 
105
161
  ## `lps logout`
106
162
 
@@ -117,15 +173,19 @@ EXAMPLES
117
173
  $ lps logout
118
174
  ```
119
175
 
120
- _See code: [src/commands/logout.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/logout.ts)_
176
+ _See code: [src/commands/logout.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/logout.ts)_
121
177
 
122
- ## `lps plugin pull`
178
+ ## `lps plugin add SLUG [VERSION]`
123
179
 
124
- Pull installed plugins from WordPress into loopress.json
180
+ Add a plugin to loopress.json (WordPress.org) or run composer require (vendor/package)
125
181
 
126
182
  ```
127
183
  USAGE
128
- $ lps plugin pull [--password <value>] [--url <value>] [--user <value>] [-d]
184
+ $ lps plugin add SLUG [VERSION] [--password <value>] [--url <value>] [--user <value>] [-d]
185
+
186
+ ARGUMENTS
187
+ SLUG Plugin slug (WordPress.org) or Composer package (vendor/package)
188
+ [VERSION] Version to pin (default: latest)
129
189
 
130
190
  FLAGS
131
191
  -d, --dry-run Show what would be written without making changes
@@ -136,26 +196,30 @@ GLOBAL FLAGS
136
196
  --user=<value> WordPress username (fallback; prefer `lps project config`)
137
197
 
138
198
  DESCRIPTION
139
- Pull installed plugins from WordPress into loopress.json
199
+ Add a plugin to loopress.json (WordPress.org) or run composer require (vendor/package)
140
200
 
141
201
  EXAMPLES
142
- $ lps plugins pull
202
+ $ lps plugin add woocommerce
143
203
 
144
- $ lps plugins pull --dry-run
204
+ $ lps plugin add woocommerce 8.9.1
205
+
206
+ $ lps plugin add wpackagist-plugin/advanced-custom-fields
207
+
208
+ $ lps plugin add contact-form-7 --dry-run
145
209
  ```
146
210
 
147
- _See code: [src/commands/plugin/pull.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/plugin/pull.ts)_
211
+ _See code: [src/commands/plugin/add.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/plugin/add.ts)_
148
212
 
149
- ## `lps plugin push`
213
+ ## `lps plugin pull`
150
214
 
151
- Sync plugins on WordPress to match loopress.json
215
+ Pull installed plugins from WordPress into loopress.json
152
216
 
153
217
  ```
154
218
  USAGE
155
- $ lps plugin push [--password <value>] [--url <value>] [--user <value>] [-d]
219
+ $ lps plugin pull [--password <value>] [--url <value>] [--user <value>] [-d]
156
220
 
157
221
  FLAGS
158
- -d, --dry-run Show what would change without making changes
222
+ -d, --dry-run Show what would be written without making changes
159
223
 
160
224
  GLOBAL FLAGS
161
225
  --password=<value> WordPress application password (fallback; prefer `lps project config`)
@@ -163,30 +227,26 @@ GLOBAL FLAGS
163
227
  --user=<value> WordPress username (fallback; prefer `lps project config`)
164
228
 
165
229
  DESCRIPTION
166
- Sync plugins on WordPress to match loopress.json
230
+ Pull installed plugins from WordPress into loopress.json
167
231
 
168
232
  EXAMPLES
169
- $ lps plugins push
233
+ $ lps plugins pull
170
234
 
171
- $ lps plugins push --dry-run
235
+ $ lps plugins pull --dry-run
172
236
  ```
173
237
 
174
- _See code: [src/commands/plugin/push.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/plugin/push.ts)_
238
+ _See code: [src/commands/plugin/pull.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/plugin/pull.ts)_
175
239
 
176
- ## `lps plugin require SLUG [VERSION]`
240
+ ## `lps plugin push`
177
241
 
178
- Add a plugin to loopress.json, resolving its latest version from WordPress.org
242
+ Sync plugins on WordPress to match loopress.json
179
243
 
180
244
  ```
181
245
  USAGE
182
- $ lps plugin require SLUG [VERSION] [--password <value>] [--url <value>] [--user <value>] [-d]
183
-
184
- ARGUMENTS
185
- SLUG Plugin slug (WordPress.org)
186
- [VERSION] Version to pin (default: latest)
246
+ $ lps plugin push [--password <value>] [--url <value>] [--user <value>] [-d]
187
247
 
188
248
  FLAGS
189
- -d, --dry-run Show what would be written without making changes
249
+ -d, --dry-run Show what would change without making changes
190
250
 
191
251
  GLOBAL FLAGS
192
252
  --password=<value> WordPress application password (fallback; prefer `lps project config`)
@@ -194,17 +254,15 @@ GLOBAL FLAGS
194
254
  --user=<value> WordPress username (fallback; prefer `lps project config`)
195
255
 
196
256
  DESCRIPTION
197
- Add a plugin to loopress.json, resolving its latest version from WordPress.org
257
+ Sync plugins on WordPress to match loopress.json
198
258
 
199
259
  EXAMPLES
200
- $ lps plugins require woocommerce
201
-
202
- $ lps plugins require woocommerce 8.9.1
260
+ $ lps plugins push
203
261
 
204
- $ lps plugins require contact-form-7 --dry-run
262
+ $ lps plugins push --dry-run
205
263
  ```
206
264
 
207
- _See code: [src/commands/plugin/require.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/plugin/require.ts)_
265
+ _See code: [src/commands/plugin/push.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/plugin/push.ts)_
208
266
 
209
267
  ## `lps project config`
210
268
 
@@ -221,7 +279,7 @@ EXAMPLES
221
279
  $ lps project config
222
280
  ```
223
281
 
224
- _See code: [src/commands/project/config.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/project/config.ts)_
282
+ _See code: [src/commands/project/config.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/project/config.ts)_
225
283
 
226
284
  ## `lps project list`
227
285
 
@@ -238,7 +296,7 @@ EXAMPLES
238
296
  $ lps project list
239
297
  ```
240
298
 
241
- _See code: [src/commands/project/list.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/project/list.ts)_
299
+ _See code: [src/commands/project/list.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/project/list.ts)_
242
300
 
243
301
  ## `lps project remove`
244
302
 
@@ -255,7 +313,7 @@ EXAMPLES
255
313
  $ lps project remove
256
314
  ```
257
315
 
258
- _See code: [src/commands/project/remove.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/project/remove.ts)_
316
+ _See code: [src/commands/project/remove.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/project/remove.ts)_
259
317
 
260
318
  ## `lps project remove-env`
261
319
 
@@ -272,7 +330,7 @@ EXAMPLES
272
330
  $ lps project remove-env
273
331
  ```
274
332
 
275
- _See code: [src/commands/project/remove-env.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/project/remove-env.ts)_
333
+ _See code: [src/commands/project/remove-env.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/project/remove-env.ts)_
276
334
 
277
335
  ## `lps project switch`
278
336
 
@@ -289,7 +347,7 @@ EXAMPLES
289
347
  $ lps project switch
290
348
  ```
291
349
 
292
- _See code: [src/commands/project/switch.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/project/switch.ts)_
350
+ _See code: [src/commands/project/switch.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/project/switch.ts)_
293
351
 
294
352
  ## `lps project switch-env`
295
353
 
@@ -306,7 +364,7 @@ EXAMPLES
306
364
  $ lps project switch-env
307
365
  ```
308
366
 
309
- _See code: [src/commands/project/switch-env.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/project/switch-env.ts)_
367
+ _See code: [src/commands/project/switch-env.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/project/switch-env.ts)_
310
368
 
311
369
  ## `lps snippet list`
312
370
 
@@ -337,7 +395,7 @@ EXAMPLES
337
395
  $ lps snippets list --plugin wpcode
338
396
  ```
339
397
 
340
- _See code: [src/commands/snippet/list.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/snippet/list.ts)_
398
+ _See code: [src/commands/snippet/list.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/snippet/list.ts)_
341
399
 
342
400
  ## `lps snippet pull [PATH]`
343
401
 
@@ -373,7 +431,7 @@ EXAMPLES
373
431
  $ lps snippets pull --plugin wpcode
374
432
  ```
375
433
 
376
- _See code: [src/commands/snippet/pull.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/snippet/pull.ts)_
434
+ _See code: [src/commands/snippet/pull.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/snippet/pull.ts)_
377
435
 
378
436
  ## `lps snippet push [PATH]`
379
437
 
@@ -409,5 +467,5 @@ EXAMPLES
409
467
  $ lps snippets push --plugin wpcode
410
468
  ```
411
469
 
412
- _See code: [src/commands/snippet/push.ts](https://github.com/loopress/loopress/blob/v0.5.0/src/commands/snippet/push.ts)_
470
+ _See code: [src/commands/snippet/push.ts](https://github.com/loopress/loopress/blob/v0.6.0/src/commands/snippet/push.ts)_
413
471
  <!-- commandsstop -->
@@ -0,0 +1,12 @@
1
+ import { LoopressCommand } from '../../lib/base.js';
2
+ export default class ComposerPull extends LoopressCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,33 @@
1
+ import { Flags } from '@oclif/core';
2
+ import got from 'got';
3
+ import { writeFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { LoopressCommand } from '../../lib/base.js';
6
+ import { readLocalConfig } from '../../utils/loopress-config.js';
7
+ export default class ComposerPull extends LoopressCommand {
8
+ static description = 'Pull composer.lock from the WordPress server';
9
+ static examples = ['$ lps composer pull', '$ lps composer pull --dry-run'];
10
+ static flags = {
11
+ ...LoopressCommand.baseFlags,
12
+ 'dry-run': Flags.boolean({ char: 'd', description: 'Show what would be written without making changes' }),
13
+ };
14
+ async run() {
15
+ const { flags } = await this.parse(ComposerPull);
16
+ const dryRun = flags['dry-run'];
17
+ const { url } = this.siteConfig;
18
+ this.log(`Pulling composer.lock from ${url}`);
19
+ const headers = await this.buildAuthHeaders();
20
+ const { composerLock } = await got
21
+ .get(`${url}/wp-json/loopress/v1/composer/lock`, { headers })
22
+ .json();
23
+ if (dryRun) {
24
+ this.log('[dry-run] Would write composer.lock');
25
+ return;
26
+ }
27
+ const localConfig = await readLocalConfig();
28
+ const rootDir = localConfig.rootDir ?? '.';
29
+ const lockPath = join(process.cwd(), rootDir, 'composer.lock');
30
+ await writeFile(lockPath, composerLock, 'utf8');
31
+ this.log(`Wrote composer.lock`);
32
+ }
33
+ }
@@ -0,0 +1,12 @@
1
+ import { PushCommand } from '../../lib/push-command.js';
2
+ export default class ComposerPush extends PushCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,49 @@
1
+ import { Flags } from '@oclif/core';
2
+ import got from 'got';
3
+ import { existsSync } from 'node:fs';
4
+ import { readFile } from 'node:fs/promises';
5
+ import { join } from 'node:path';
6
+ import { PushCommand } from '../../lib/push-command.js';
7
+ import { readLocalConfig } from '../../utils/loopress-config.js';
8
+ export default class ComposerPush extends PushCommand {
9
+ static description = 'Upload composer.json and composer.lock to WordPress and run composer install';
10
+ static examples = ['$ lps composer push', '$ lps composer push --dry-run'];
11
+ static flags = {
12
+ ...PushCommand.baseFlags,
13
+ 'dry-run': Flags.boolean({ char: 'd', description: 'Show what would change without making changes' }),
14
+ };
15
+ async run() {
16
+ const { flags } = await this.parse(ComposerPush);
17
+ const dryRun = flags['dry-run'];
18
+ this.dryRun = dryRun;
19
+ const { url } = this.siteConfig;
20
+ const localConfig = await readLocalConfig();
21
+ const rootDir = localConfig.rootDir ?? '.';
22
+ const composerJsonPath = join(process.cwd(), rootDir, 'composer.json');
23
+ const composerLockPath = join(process.cwd(), rootDir, 'composer.lock');
24
+ if (!existsSync(composerJsonPath)) {
25
+ this.error(`No composer.json found at ${composerJsonPath}`);
26
+ }
27
+ const composerJsonRaw = await readFile(composerJsonPath, 'utf8');
28
+ const parsed = JSON.parse(composerJsonRaw);
29
+ const packageCount = Object.keys(parsed.require ?? {}).length;
30
+ const hasLock = existsSync(composerLockPath);
31
+ const composerLockRaw = hasLock ? await readFile(composerLockPath, 'utf8') : null;
32
+ this.log(`Pushing composer.json (${packageCount} ${packageCount === 1 ? 'package' : 'packages'}) to ${url}`);
33
+ if (composerLockRaw) {
34
+ this.log(' + composer.lock included (reproducible install)');
35
+ }
36
+ else {
37
+ this.warn('No composer.lock found. The server will resolve versions freely.');
38
+ }
39
+ if (dryRun)
40
+ return;
41
+ const headers = await this.buildAuthHeaders();
42
+ await got.post(`${url}/wp-json/loopress/v1/composer/sync`, {
43
+ headers,
44
+ json: { composerJson: composerJsonRaw, composerLock: composerLockRaw },
45
+ });
46
+ this.log('composer install completed on the server.');
47
+ await this.recordSuccess();
48
+ }
49
+ }
@@ -1,6 +1,6 @@
1
1
  import { LoopressCommand } from '../../lib/base.js';
2
2
  export declare function resolvePluginVersion(slug: string, version: string): Promise<string>;
3
- export default class Require extends LoopressCommand {
3
+ export default class Add extends LoopressCommand {
4
4
  static args: {
5
5
  slug: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
6
  version: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
@@ -14,4 +14,6 @@ export default class Require extends LoopressCommand {
14
14
  user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
15
  };
16
16
  run(): Promise<void>;
17
+ private requireComposerPackage;
18
+ private requireWpOrgPlugin;
17
19
  }
@@ -1,5 +1,6 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import got from 'got';
3
+ import { spawnSync } from 'node:child_process';
3
4
  import { LoopressCommand } from '../../lib/base.js';
4
5
  import { readLocalConfig, writeLocalConfig } from '../../utils/loopress-config.js';
5
6
  const WP_ORG_API = 'https://api.wordpress.org/plugins/info/1.2/';
@@ -24,26 +25,46 @@ export async function resolvePluginVersion(slug, version) {
24
25
  throw new Error(`Plugin "${slug}" not found on WordPress.org.`);
25
26
  return info.version;
26
27
  }
27
- export default class Require extends LoopressCommand {
28
+ export default class Add extends LoopressCommand {
28
29
  static args = {
29
- slug: Args.string({ description: 'Plugin slug (WordPress.org)', required: true }),
30
+ slug: Args.string({ description: 'Plugin slug (WordPress.org) or Composer package (vendor/package)', required: true }),
30
31
  version: Args.string({ description: 'Version to pin (default: latest)' }),
31
32
  };
32
- static description = 'Add a plugin to loopress.json, resolving its latest version from WordPress.org';
33
+ static description = 'Add a plugin to loopress.json (WordPress.org) or run composer require (vendor/package)';
33
34
  static examples = [
34
- '$ lps plugins require woocommerce',
35
- '$ lps plugins require woocommerce 8.9.1',
36
- '$ lps plugins require contact-form-7 --dry-run',
35
+ '$ lps plugin add woocommerce',
36
+ '$ lps plugin add woocommerce 8.9.1',
37
+ '$ lps plugin add wpackagist-plugin/advanced-custom-fields',
38
+ '$ lps plugin add contact-form-7 --dry-run',
37
39
  ];
38
40
  static flags = {
39
41
  ...LoopressCommand.baseFlags,
40
42
  'dry-run': Flags.boolean({ char: 'd', description: 'Show what would be written without making changes' }),
41
43
  };
42
44
  async run() {
43
- const { args, flags } = await this.parse(Require);
45
+ const { args, flags } = await this.parse(Add);
44
46
  const dryRun = flags['dry-run'];
45
47
  const { slug } = args;
46
48
  const requestedVersion = args.version ?? 'latest';
49
+ if (slug.includes('/')) {
50
+ await this.requireComposerPackage(slug, requestedVersion, dryRun);
51
+ return;
52
+ }
53
+ await this.requireWpOrgPlugin(slug, requestedVersion, dryRun);
54
+ }
55
+ async requireComposerPackage(pkg, version, dryRun) {
56
+ const composerArg = version === 'latest' ? pkg : `${pkg}:${version}`;
57
+ this.log(`Running: composer require ${composerArg}`);
58
+ if (dryRun) {
59
+ this.log(`[dry-run] Would run: composer require ${composerArg}`);
60
+ return;
61
+ }
62
+ const result = spawnSync('composer', ['require', composerArg], { stdio: 'inherit' });
63
+ if (result.status !== 0) {
64
+ this.error('composer require failed. Make sure Composer is installed and accessible.');
65
+ }
66
+ }
67
+ async requireWpOrgPlugin(slug, requestedVersion, dryRun) {
47
68
  this.log(`Resolving ${slug}@${requestedVersion}...`);
48
69
  let resolvedVersion;
49
70
  try {
@@ -56,7 +77,7 @@ export default class Require extends LoopressCommand {
56
77
  const localConfig = await readLocalConfig();
57
78
  const existing = localConfig.plugins ?? {};
58
79
  if (existing[slug] === resolvedVersion) {
59
- this.log(`${slug}@${resolvedVersion} is already in loopress.json nothing to do.`);
80
+ this.log(`${slug}@${resolvedVersion} is already in loopress.json, nothing to do.`);
60
81
  return;
61
82
  }
62
83
  const updated = existing[slug] !== undefined;
@@ -1,6 +1,7 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import got from 'got';
3
3
  import { LoopressCommand } from '../../lib/base.js';
4
+ import { getComposerManagedSlugs, readComposerJson } from '../../utils/composer.js';
4
5
  import { readLocalConfig, writeLocalConfig } from '../../utils/loopress-config.js';
5
6
  import { mergePluginManifest } from '../../utils/plugins.js';
6
7
  export default class Pull extends LoopressCommand {
@@ -17,7 +18,15 @@ export default class Pull extends LoopressCommand {
17
18
  this.log(`Pulling plugins from ${url}`);
18
19
  const headers = await this.buildAuthHeaders();
19
20
  const installed = await got.get(`${url}/wp-json/loopress/v1/plugins`, { headers }).json();
20
- const incoming = Object.fromEntries(installed.map((p) => [p.slug, p.version]));
21
+ const composerJson = await readComposerJson();
22
+ const composerSlugs = composerJson ? getComposerManagedSlugs(composerJson) : [];
23
+ const incoming = Object.fromEntries(installed.filter((p) => !composerSlugs.includes(p.slug)).map((p) => [p.slug, p.version]));
24
+ if (composerSlugs.length > 0) {
25
+ const found = installed.filter((p) => composerSlugs.includes(p.slug)).map((p) => p.slug);
26
+ if (found.length > 0) {
27
+ this.log(`Skipping ${found.length} Composer-managed ${found.length === 1 ? 'plugin' : 'plugins'}: ${found.join(', ')}`);
28
+ }
29
+ }
21
30
  const localConfig = await readLocalConfig();
22
31
  const { added, merged, updated } = mergePluginManifest(localConfig.plugins ?? {}, incoming);
23
32
  if (dryRun) {
@@ -2,6 +2,7 @@ import { confirm } from '@inquirer/prompts';
2
2
  import { Flags } from '@oclif/core';
3
3
  import got from 'got';
4
4
  import { PushCommand } from '../../lib/push-command.js';
5
+ import { getComposerManagedSlugs, readComposerJson } from '../../utils/composer.js';
5
6
  import { readLocalConfig } from '../../utils/loopress-config.js';
6
7
  import { diffPlugins } from '../../utils/plugins.js';
7
8
  export default class Push extends PushCommand {
@@ -21,10 +22,18 @@ export default class Push extends PushCommand {
21
22
  if (!manifest || Object.keys(manifest).length === 0) {
22
23
  this.error('No plugins found in loopress.json. Run `lps plugins pull` first.');
23
24
  }
25
+ const composerJson = await readComposerJson();
26
+ const composerSlugs = composerJson ? getComposerManagedSlugs(composerJson) : [];
27
+ const filteredManifest = Object.fromEntries(Object.entries(manifest).filter(([slug]) => !composerSlugs.includes(slug)));
28
+ const skipped = composerSlugs.filter((slug) => slug in manifest);
29
+ if (skipped.length > 0) {
30
+ this.log(`Skipping ${skipped.length} Composer-managed ${skipped.length === 1 ? 'plugin' : 'plugins'}: ${skipped.join(', ')}`);
31
+ this.log('Run `lps composer push` to deploy them.');
32
+ }
24
33
  this.log(`Pushing plugins to ${url}`);
25
34
  const headers = await this.buildAuthHeaders();
26
35
  const installed = await got.get(`${url}/wp-json/loopress/v1/plugins`, { headers }).json();
27
- const { drifted, toActivate, toInstall } = diffPlugins(manifest, installed);
36
+ const { drifted, toActivate, toInstall } = diffPlugins(filteredManifest, installed);
28
37
  if (toInstall.length === 0 && toActivate.length === 0 && drifted.length === 0) {
29
38
  this.log('Everything is already in sync.');
30
39
  return;
@@ -0,0 +1,7 @@
1
+ export interface ComposerJson {
2
+ require?: Record<string, string>;
3
+ 'require-dev'?: Record<string, string>;
4
+ }
5
+ export declare function readComposerJson(): Promise<ComposerJson | null>;
6
+ export declare function readComposerLock(): Promise<null | string>;
7
+ export declare function getComposerManagedSlugs(composerJson: ComposerJson): string[];
@@ -0,0 +1,33 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ export async function readComposerJson() {
5
+ const path = join(process.cwd(), 'composer.json');
6
+ if (!existsSync(path))
7
+ return null;
8
+ try {
9
+ const content = await readFile(path, 'utf8');
10
+ return JSON.parse(content);
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ export async function readComposerLock() {
17
+ const path = join(process.cwd(), 'composer.lock');
18
+ if (!existsSync(path))
19
+ return null;
20
+ try {
21
+ return await readFile(path, 'utf8');
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ // Returns WordPress plugin slugs declared as wpackagist-plugin/* in composer.json
28
+ export function getComposerManagedSlugs(composerJson) {
29
+ const packages = { ...composerJson.require, ...composerJson['require-dev'] };
30
+ return Object.keys(packages)
31
+ .filter((pkg) => pkg.startsWith('wpackagist-plugin/'))
32
+ .map((pkg) => pkg.slice('wpackagist-plugin/'.length));
33
+ }
@@ -69,13 +69,13 @@
69
69
  "logout.js"
70
70
  ]
71
71
  },
72
- "plugin:pull": {
72
+ "composer:pull": {
73
73
  "aliases": [],
74
74
  "args": {},
75
- "description": "Pull installed plugins from WordPress into loopress.json",
75
+ "description": "Pull composer.lock from the WordPress server",
76
76
  "examples": [
77
- "$ lps plugins pull",
78
- "$ lps plugins pull --dry-run"
77
+ "$ lps composer pull",
78
+ "$ lps composer pull --dry-run"
79
79
  ],
80
80
  "flags": {
81
81
  "password": {
@@ -112,7 +112,7 @@
112
112
  },
113
113
  "hasDynamicHelp": false,
114
114
  "hiddenAliases": [],
115
- "id": "plugin:pull",
115
+ "id": "composer:pull",
116
116
  "pluginAlias": "@loopress/cli",
117
117
  "pluginName": "@loopress/cli",
118
118
  "pluginType": "core",
@@ -122,17 +122,17 @@
122
122
  "relativePath": [
123
123
  "dist",
124
124
  "commands",
125
- "plugin",
125
+ "composer",
126
126
  "pull.js"
127
127
  ]
128
128
  },
129
- "plugin:push": {
129
+ "composer:push": {
130
130
  "aliases": [],
131
131
  "args": {},
132
- "description": "Sync plugins on WordPress to match loopress.json",
132
+ "description": "Upload composer.json and composer.lock to WordPress and run composer install",
133
133
  "examples": [
134
- "$ lps plugins push",
135
- "$ lps plugins push --dry-run"
134
+ "$ lps composer push",
135
+ "$ lps composer push --dry-run"
136
136
  ],
137
137
  "flags": {
138
138
  "password": {
@@ -169,7 +169,7 @@
169
169
  },
170
170
  "hasDynamicHelp": false,
171
171
  "hiddenAliases": [],
172
- "id": "plugin:push",
172
+ "id": "composer:push",
173
173
  "pluginAlias": "@loopress/cli",
174
174
  "pluginName": "@loopress/cli",
175
175
  "pluginType": "core",
@@ -178,15 +178,15 @@
178
178
  "relativePath": [
179
179
  "dist",
180
180
  "commands",
181
- "plugin",
181
+ "composer",
182
182
  "push.js"
183
183
  ]
184
184
  },
185
- "plugin:require": {
185
+ "plugin:add": {
186
186
  "aliases": [],
187
187
  "args": {
188
188
  "slug": {
189
- "description": "Plugin slug (WordPress.org)",
189
+ "description": "Plugin slug (WordPress.org) or Composer package (vendor/package)",
190
190
  "name": "slug",
191
191
  "required": true
192
192
  },
@@ -195,11 +195,12 @@
195
195
  "name": "version"
196
196
  }
197
197
  },
198
- "description": "Add a plugin to loopress.json, resolving its latest version from WordPress.org",
198
+ "description": "Add a plugin to loopress.json (WordPress.org) or run composer require (vendor/package)",
199
199
  "examples": [
200
- "$ lps plugins require woocommerce",
201
- "$ lps plugins require woocommerce 8.9.1",
202
- "$ lps plugins require contact-form-7 --dry-run"
200
+ "$ lps plugin add woocommerce",
201
+ "$ lps plugin add woocommerce 8.9.1",
202
+ "$ lps plugin add wpackagist-plugin/advanced-custom-fields",
203
+ "$ lps plugin add contact-form-7 --dry-run"
203
204
  ],
204
205
  "flags": {
205
206
  "password": {
@@ -236,7 +237,7 @@
236
237
  },
237
238
  "hasDynamicHelp": false,
238
239
  "hiddenAliases": [],
239
- "id": "plugin:require",
240
+ "id": "plugin:add",
240
241
  "pluginAlias": "@loopress/cli",
241
242
  "pluginName": "@loopress/cli",
242
243
  "pluginType": "core",
@@ -247,116 +248,53 @@
247
248
  "dist",
248
249
  "commands",
249
250
  "plugin",
250
- "require.js"
251
- ]
252
- },
253
- "project:config": {
254
- "aliases": [],
255
- "args": {},
256
- "description": "Add or update a WordPress project environment",
257
- "examples": [
258
- "$ lps project config"
259
- ],
260
- "flags": {},
261
- "hasDynamicHelp": false,
262
- "hiddenAliases": [],
263
- "id": "project:config",
264
- "pluginAlias": "@loopress/cli",
265
- "pluginName": "@loopress/cli",
266
- "pluginType": "core",
267
- "strict": true,
268
- "enableJsonFlag": false,
269
- "isESM": true,
270
- "relativePath": [
271
- "dist",
272
- "commands",
273
- "project",
274
- "config.js"
275
- ]
276
- },
277
- "project:list": {
278
- "aliases": [],
279
- "args": {},
280
- "description": "List configured WordPress projects",
281
- "examples": [
282
- "$ lps project list"
283
- ],
284
- "flags": {},
285
- "hasDynamicHelp": false,
286
- "hiddenAliases": [],
287
- "id": "project:list",
288
- "pluginAlias": "@loopress/cli",
289
- "pluginName": "@loopress/cli",
290
- "pluginType": "core",
291
- "strict": true,
292
- "enableJsonFlag": false,
293
- "isESM": true,
294
- "relativePath": [
295
- "dist",
296
- "commands",
297
- "project",
298
- "list.js"
299
- ]
300
- },
301
- "project:remove-env": {
302
- "aliases": [],
303
- "args": {},
304
- "description": "Remove one or more environments from the current project",
305
- "examples": [
306
- "$ lps project remove-env"
307
- ],
308
- "flags": {},
309
- "hasDynamicHelp": false,
310
- "hiddenAliases": [],
311
- "id": "project:remove-env",
312
- "pluginAlias": "@loopress/cli",
313
- "pluginName": "@loopress/cli",
314
- "pluginType": "core",
315
- "strict": true,
316
- "enableJsonFlag": false,
317
- "isESM": true,
318
- "relativePath": [
319
- "dist",
320
- "commands",
321
- "project",
322
- "remove-env.js"
323
- ]
324
- },
325
- "project:remove": {
326
- "aliases": [],
327
- "args": {},
328
- "description": "Remove one or more WordPress project configurations",
329
- "examples": [
330
- "$ lps project remove"
331
- ],
332
- "flags": {},
333
- "hasDynamicHelp": false,
334
- "hiddenAliases": [],
335
- "id": "project:remove",
336
- "pluginAlias": "@loopress/cli",
337
- "pluginName": "@loopress/cli",
338
- "pluginType": "core",
339
- "strict": true,
340
- "enableJsonFlag": false,
341
- "isESM": true,
342
- "relativePath": [
343
- "dist",
344
- "commands",
345
- "project",
346
- "remove.js"
251
+ "add.js"
347
252
  ]
348
253
  },
349
- "project:switch-env": {
254
+ "plugin:pull": {
350
255
  "aliases": [],
351
256
  "args": {},
352
- "description": "Switch the active environment within the current project",
257
+ "description": "Pull installed plugins from WordPress into loopress.json",
353
258
  "examples": [
354
- "$ lps project switch-env"
259
+ "$ lps plugins pull",
260
+ "$ lps plugins pull --dry-run"
355
261
  ],
356
- "flags": {},
262
+ "flags": {
263
+ "password": {
264
+ "description": "WordPress application password (fallback; prefer `lps project config`)",
265
+ "helpGroup": "GLOBAL",
266
+ "name": "password",
267
+ "hasDynamicHelp": false,
268
+ "multiple": false,
269
+ "type": "option"
270
+ },
271
+ "url": {
272
+ "description": "WordPress URL (fallback; prefer `lps project config`)",
273
+ "helpGroup": "GLOBAL",
274
+ "name": "url",
275
+ "hasDynamicHelp": false,
276
+ "multiple": false,
277
+ "type": "option"
278
+ },
279
+ "user": {
280
+ "description": "WordPress username (fallback; prefer `lps project config`)",
281
+ "helpGroup": "GLOBAL",
282
+ "name": "user",
283
+ "hasDynamicHelp": false,
284
+ "multiple": false,
285
+ "type": "option"
286
+ },
287
+ "dry-run": {
288
+ "char": "d",
289
+ "description": "Show what would be written without making changes",
290
+ "name": "dry-run",
291
+ "allowNo": false,
292
+ "type": "boolean"
293
+ }
294
+ },
357
295
  "hasDynamicHelp": false,
358
296
  "hiddenAliases": [],
359
- "id": "project:switch-env",
297
+ "id": "plugin:pull",
360
298
  "pluginAlias": "@loopress/cli",
361
299
  "pluginName": "@loopress/cli",
362
300
  "pluginType": "core",
@@ -366,32 +304,64 @@
366
304
  "relativePath": [
367
305
  "dist",
368
306
  "commands",
369
- "project",
370
- "switch-env.js"
307
+ "plugin",
308
+ "pull.js"
371
309
  ]
372
310
  },
373
- "project:switch": {
311
+ "plugin:push": {
374
312
  "aliases": [],
375
313
  "args": {},
376
- "description": "Switch the active project",
314
+ "description": "Sync plugins on WordPress to match loopress.json",
377
315
  "examples": [
378
- "$ lps project switch"
316
+ "$ lps plugins push",
317
+ "$ lps plugins push --dry-run"
379
318
  ],
380
- "flags": {},
319
+ "flags": {
320
+ "password": {
321
+ "description": "WordPress application password (fallback; prefer `lps project config`)",
322
+ "helpGroup": "GLOBAL",
323
+ "name": "password",
324
+ "hasDynamicHelp": false,
325
+ "multiple": false,
326
+ "type": "option"
327
+ },
328
+ "url": {
329
+ "description": "WordPress URL (fallback; prefer `lps project config`)",
330
+ "helpGroup": "GLOBAL",
331
+ "name": "url",
332
+ "hasDynamicHelp": false,
333
+ "multiple": false,
334
+ "type": "option"
335
+ },
336
+ "user": {
337
+ "description": "WordPress username (fallback; prefer `lps project config`)",
338
+ "helpGroup": "GLOBAL",
339
+ "name": "user",
340
+ "hasDynamicHelp": false,
341
+ "multiple": false,
342
+ "type": "option"
343
+ },
344
+ "dry-run": {
345
+ "char": "d",
346
+ "description": "Show what would change without making changes",
347
+ "name": "dry-run",
348
+ "allowNo": false,
349
+ "type": "boolean"
350
+ }
351
+ },
381
352
  "hasDynamicHelp": false,
382
353
  "hiddenAliases": [],
383
- "id": "project:switch",
354
+ "id": "plugin:push",
384
355
  "pluginAlias": "@loopress/cli",
385
356
  "pluginName": "@loopress/cli",
386
357
  "pluginType": "core",
387
358
  "strict": true,
388
- "enableJsonFlag": false,
389
359
  "isESM": true,
390
360
  "relativePath": [
391
361
  "dist",
392
362
  "commands",
393
- "project",
394
- "switch.js"
363
+ "plugin",
364
+ "push.js"
395
365
  ]
396
366
  },
397
367
  "snippet:list": {
@@ -614,7 +584,151 @@
614
584
  "snippet",
615
585
  "push.js"
616
586
  ]
587
+ },
588
+ "project:config": {
589
+ "aliases": [],
590
+ "args": {},
591
+ "description": "Add or update a WordPress project environment",
592
+ "examples": [
593
+ "$ lps project config"
594
+ ],
595
+ "flags": {},
596
+ "hasDynamicHelp": false,
597
+ "hiddenAliases": [],
598
+ "id": "project:config",
599
+ "pluginAlias": "@loopress/cli",
600
+ "pluginName": "@loopress/cli",
601
+ "pluginType": "core",
602
+ "strict": true,
603
+ "enableJsonFlag": false,
604
+ "isESM": true,
605
+ "relativePath": [
606
+ "dist",
607
+ "commands",
608
+ "project",
609
+ "config.js"
610
+ ]
611
+ },
612
+ "project:list": {
613
+ "aliases": [],
614
+ "args": {},
615
+ "description": "List configured WordPress projects",
616
+ "examples": [
617
+ "$ lps project list"
618
+ ],
619
+ "flags": {},
620
+ "hasDynamicHelp": false,
621
+ "hiddenAliases": [],
622
+ "id": "project:list",
623
+ "pluginAlias": "@loopress/cli",
624
+ "pluginName": "@loopress/cli",
625
+ "pluginType": "core",
626
+ "strict": true,
627
+ "enableJsonFlag": false,
628
+ "isESM": true,
629
+ "relativePath": [
630
+ "dist",
631
+ "commands",
632
+ "project",
633
+ "list.js"
634
+ ]
635
+ },
636
+ "project:remove-env": {
637
+ "aliases": [],
638
+ "args": {},
639
+ "description": "Remove one or more environments from the current project",
640
+ "examples": [
641
+ "$ lps project remove-env"
642
+ ],
643
+ "flags": {},
644
+ "hasDynamicHelp": false,
645
+ "hiddenAliases": [],
646
+ "id": "project:remove-env",
647
+ "pluginAlias": "@loopress/cli",
648
+ "pluginName": "@loopress/cli",
649
+ "pluginType": "core",
650
+ "strict": true,
651
+ "enableJsonFlag": false,
652
+ "isESM": true,
653
+ "relativePath": [
654
+ "dist",
655
+ "commands",
656
+ "project",
657
+ "remove-env.js"
658
+ ]
659
+ },
660
+ "project:remove": {
661
+ "aliases": [],
662
+ "args": {},
663
+ "description": "Remove one or more WordPress project configurations",
664
+ "examples": [
665
+ "$ lps project remove"
666
+ ],
667
+ "flags": {},
668
+ "hasDynamicHelp": false,
669
+ "hiddenAliases": [],
670
+ "id": "project:remove",
671
+ "pluginAlias": "@loopress/cli",
672
+ "pluginName": "@loopress/cli",
673
+ "pluginType": "core",
674
+ "strict": true,
675
+ "enableJsonFlag": false,
676
+ "isESM": true,
677
+ "relativePath": [
678
+ "dist",
679
+ "commands",
680
+ "project",
681
+ "remove.js"
682
+ ]
683
+ },
684
+ "project:switch-env": {
685
+ "aliases": [],
686
+ "args": {},
687
+ "description": "Switch the active environment within the current project",
688
+ "examples": [
689
+ "$ lps project switch-env"
690
+ ],
691
+ "flags": {},
692
+ "hasDynamicHelp": false,
693
+ "hiddenAliases": [],
694
+ "id": "project:switch-env",
695
+ "pluginAlias": "@loopress/cli",
696
+ "pluginName": "@loopress/cli",
697
+ "pluginType": "core",
698
+ "strict": true,
699
+ "enableJsonFlag": false,
700
+ "isESM": true,
701
+ "relativePath": [
702
+ "dist",
703
+ "commands",
704
+ "project",
705
+ "switch-env.js"
706
+ ]
707
+ },
708
+ "project:switch": {
709
+ "aliases": [],
710
+ "args": {},
711
+ "description": "Switch the active project",
712
+ "examples": [
713
+ "$ lps project switch"
714
+ ],
715
+ "flags": {},
716
+ "hasDynamicHelp": false,
717
+ "hiddenAliases": [],
718
+ "id": "project:switch",
719
+ "pluginAlias": "@loopress/cli",
720
+ "pluginName": "@loopress/cli",
721
+ "pluginType": "core",
722
+ "strict": true,
723
+ "enableJsonFlag": false,
724
+ "isESM": true,
725
+ "relativePath": [
726
+ "dist",
727
+ "commands",
728
+ "project",
729
+ "switch.js"
730
+ ]
617
731
  }
618
732
  },
619
- "version": "0.5.0"
733
+ "version": "0.6.0"
620
734
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loopress/cli",
3
3
  "description": "CLI tool for syncing WordPress CodeSnippets, styles, and menus via the REST API",
4
- "version": "0.5.0",
4
+ "version": "0.6.0",
5
5
  "author": "jean-smaug",
6
6
  "bin": {
7
7
  "loopress": "bin/run.js",
@@ -13,7 +13,7 @@
13
13
  "dependencies": {
14
14
  "@inquirer/prompts": "^8.5.2",
15
15
  "@oclif/core": "^4.11.11",
16
- "@oclif/plugin-help": "^6.2.52",
16
+ "@oclif/plugin-help": "^6.2.53",
17
17
  "@oclif/plugin-plugins": "^5.4.80",
18
18
  "glob": "13.0.6",
19
19
  "got": "^15.0.7",
@@ -27,12 +27,12 @@
27
27
  "eslint": "^9",
28
28
  "eslint-config-oclif": "^6.0.174",
29
29
  "eslint-config-prettier": "^10",
30
- "oclif": "^4.23.22",
31
- "prettier": "3.9.1",
30
+ "oclif": "^4.23.24",
31
+ "prettier": "3.9.4",
32
32
  "shx": "^0.4.0",
33
33
  "tsx": "4.22.4",
34
34
  "typescript": "^6.0.3",
35
- "vitest": "^3.2.4"
35
+ "vitest": "^4.1.9"
36
36
  },
37
37
  "engines": {
38
38
  "node": ">=18.0.0"
@@ -48,6 +48,10 @@
48
48
  ],
49
49
  "license": "MPL-2.0",
50
50
  "main": "dist/index.js",
51
+ "exports": {
52
+ ".": "./dist/index.js",
53
+ "./schema": "./loopress.schema.json"
54
+ },
51
55
  "type": "module",
52
56
  "oclif": {
53
57
  "bin": "lps",