@loopress/cli 0.11.0 → 0.12.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.11.0 linux-x64 node-v24.18.0
24
+ @loopress/cli/0.12.0 linux-x64 node-v24.18.0
25
25
  $ lps --help [COMMAND]
26
26
  USAGE
27
27
  $ lps COMMAND
@@ -85,7 +85,7 @@ EXAMPLES
85
85
  $ lps composer pull --dry-run
86
86
  ```
87
87
 
88
- _See code: [src/commands/composer/pull.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/composer/pull.ts)_
88
+ _See code: [src/commands/composer/pull.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/composer/pull.ts)_
89
89
 
90
90
  ## `lps composer push`
91
91
 
@@ -107,7 +107,7 @@ EXAMPLES
107
107
  $ lps composer push --dry-run
108
108
  ```
109
109
 
110
- _See code: [src/commands/composer/push.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/composer/push.ts)_
110
+ _See code: [src/commands/composer/push.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/composer/push.ts)_
111
111
 
112
112
  ## `lps help [COMMAND]`
113
113
 
@@ -144,7 +144,7 @@ EXAMPLES
144
144
  $ lps init
145
145
  ```
146
146
 
147
- _See code: [src/commands/init.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/init.ts)_
147
+ _See code: [src/commands/init.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/init.ts)_
148
148
 
149
149
  ## `lps login`
150
150
 
@@ -161,7 +161,7 @@ EXAMPLES
161
161
  $ lps login
162
162
  ```
163
163
 
164
- _See code: [src/commands/login.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/login.ts)_
164
+ _See code: [src/commands/login.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/login.ts)_
165
165
 
166
166
  ## `lps logout`
167
167
 
@@ -178,7 +178,7 @@ EXAMPLES
178
178
  $ lps logout
179
179
  ```
180
180
 
181
- _See code: [src/commands/logout.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/logout.ts)_
181
+ _See code: [src/commands/logout.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/logout.ts)_
182
182
 
183
183
  ## `lps plugin add SLUG [VERSION]`
184
184
 
@@ -206,7 +206,7 @@ EXAMPLES
206
206
  $ lps plugin add contact-form-7 --dry-run
207
207
  ```
208
208
 
209
- _See code: [src/commands/plugin/add.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/plugin/add.ts)_
209
+ _See code: [src/commands/plugin/add.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/plugin/add.ts)_
210
210
 
211
211
  ## `lps plugin pull`
212
212
 
@@ -228,7 +228,7 @@ EXAMPLES
228
228
  $ lps plugin pull --dry-run
229
229
  ```
230
230
 
231
- _See code: [src/commands/plugin/pull.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/plugin/pull.ts)_
231
+ _See code: [src/commands/plugin/pull.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/plugin/pull.ts)_
232
232
 
233
233
  ## `lps plugin push`
234
234
 
@@ -250,7 +250,7 @@ EXAMPLES
250
250
  $ lps plugin push --dry-run
251
251
  ```
252
252
 
253
- _See code: [src/commands/plugin/push.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/plugin/push.ts)_
253
+ _See code: [src/commands/plugin/push.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/plugin/push.ts)_
254
254
 
255
255
  ## `lps project config`
256
256
 
@@ -267,7 +267,7 @@ EXAMPLES
267
267
  $ lps project config
268
268
  ```
269
269
 
270
- _See code: [src/commands/project/config.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/project/config.ts)_
270
+ _See code: [src/commands/project/config.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/project/config.ts)_
271
271
 
272
272
  ## `lps project list`
273
273
 
@@ -284,7 +284,7 @@ EXAMPLES
284
284
  $ lps project list
285
285
  ```
286
286
 
287
- _See code: [src/commands/project/list.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/project/list.ts)_
287
+ _See code: [src/commands/project/list.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/project/list.ts)_
288
288
 
289
289
  ## `lps project remove`
290
290
 
@@ -301,7 +301,7 @@ EXAMPLES
301
301
  $ lps project remove
302
302
  ```
303
303
 
304
- _See code: [src/commands/project/remove.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/project/remove.ts)_
304
+ _See code: [src/commands/project/remove.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/project/remove.ts)_
305
305
 
306
306
  ## `lps project switch`
307
307
 
@@ -318,7 +318,7 @@ EXAMPLES
318
318
  $ lps project switch
319
319
  ```
320
320
 
321
- _See code: [src/commands/project/switch.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/project/switch.ts)_
321
+ _See code: [src/commands/project/switch.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/project/switch.ts)_
322
322
 
323
323
  ## `lps project sync`
324
324
 
@@ -335,7 +335,7 @@ EXAMPLES
335
335
  $ lps project sync
336
336
  ```
337
337
 
338
- _See code: [src/commands/project/sync.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/project/sync.ts)_
338
+ _See code: [src/commands/project/sync.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/project/sync.ts)_
339
339
 
340
340
  ## `lps snippet list`
341
341
 
@@ -355,7 +355,7 @@ EXAMPLES
355
355
  $ lps snippet list
356
356
  ```
357
357
 
358
- _See code: [src/commands/snippet/list.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/snippet/list.ts)_
358
+ _See code: [src/commands/snippet/list.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/snippet/list.ts)_
359
359
 
360
360
  ## `lps snippet pull [PATH]`
361
361
 
@@ -380,7 +380,7 @@ EXAMPLES
380
380
  $ lps snippet pull --path ./snippets
381
381
  ```
382
382
 
383
- _See code: [src/commands/snippet/pull.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/snippet/pull.ts)_
383
+ _See code: [src/commands/snippet/pull.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/snippet/pull.ts)_
384
384
 
385
385
  ## `lps snippet push [PATH]`
386
386
 
@@ -406,7 +406,7 @@ EXAMPLES
406
406
  $ lps snippet push --path ./snippets
407
407
  ```
408
408
 
409
- _See code: [src/commands/snippet/push.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/snippet/push.ts)_
409
+ _See code: [src/commands/snippet/push.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/snippet/push.ts)_
410
410
 
411
411
  ## `lps status`
412
412
 
@@ -423,5 +423,5 @@ EXAMPLES
423
423
  $ lps status
424
424
  ```
425
425
 
426
- _See code: [src/commands/status.ts](https://github.com/loopress/loopress/blob/v0.11.0/src/commands/status.ts)_
426
+ _See code: [src/commands/status.ts](https://github.com/loopress/loopress/blob/v0.12.0/src/commands/status.ts)_
427
427
  <!-- commandsstop -->
@@ -184,12 +184,22 @@ export default class Sync extends Command {
184
184
  return { action: 'create', project };
185
185
  }
186
186
  pullProject(apiProject, task) {
187
- const environments = {};
187
+ // Reuse a local project already linked to this API project instead of always minting a
188
+ // new one: otherwise every sync run that can't "claim" an existing link (e.g. after the
189
+ // local config was reset or desynced from the API) creates yet another duplicate entry.
190
+ const existing = configManager.findProjectByApiId(apiProject.id);
191
+ const environments = { ...existing?.environments };
188
192
  for (const env of apiProject.environments) {
189
- environments[env.name] = { addedAt: env.createdAt, apiEnvironmentId: env.id, name: env.name, url: env.url };
190
- }
191
- configManager.setProject(configManager.createProjectId(apiProject.name), {
192
- addedAt: apiProject.createdAt,
193
+ environments[env.name] = {
194
+ ...environments[env.name],
195
+ addedAt: environments[env.name]?.addedAt ?? env.createdAt,
196
+ apiEnvironmentId: env.id,
197
+ name: env.name,
198
+ url: env.url,
199
+ };
200
+ }
201
+ configManager.setProject(existing?.id ?? configManager.createProjectId(apiProject.name), {
202
+ addedAt: existing?.addedAt ?? apiProject.createdAt,
193
203
  apiProjectId: apiProject.id,
194
204
  environments,
195
205
  name: apiProject.name,
@@ -12,4 +12,5 @@ export default class Pull extends LoopressCommand {
12
12
  'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
13
  };
14
14
  run(): Promise<void>;
15
+ private findOrphanedFiles;
15
16
  }
@@ -1,7 +1,7 @@
1
1
  import { Args } from '@oclif/core';
2
2
  import { Listr } from 'listr2';
3
- import { mkdir, writeFile } from 'node:fs/promises';
4
- import { join } from 'node:path';
3
+ import { mkdir, readdir, rm, writeFile } from 'node:fs/promises';
4
+ import { extname, join } from 'node:path';
5
5
  import slugify from 'slugify';
6
6
  import { LoopressCommand } from '../../lib/base.js';
7
7
  import { normalizeSnippet, SNIPPETS_ENDPOINT } from '../../utils/snippet-format.js';
@@ -55,13 +55,20 @@ export default class Pull extends LoopressCommand {
55
55
  this.log(`Snippets path: ${path}`);
56
56
  const remoteList = await this.wp.get(SNIPPETS_ENDPOINT);
57
57
  const snippets = remoteList.map((r) => normalizeSnippet(r));
58
+ const pullable = snippets.filter((snippet) => snippet.name.trim());
59
+ const skipped = snippets.length - pullable.length;
60
+ // Files following the `<id>-<slug>` convention whose id is no longer in the current
61
+ // remote list belong to a snippet that was deleted on WordPress. Left on disk, they'd
62
+ // silently come back to life the next time `snippet push` runs.
63
+ const orphans = await this.findOrphanedFiles(path, new Set(pullable.map((snippet) => snippet.id)));
58
64
  if (this.dryRun) {
59
65
  this.log(`[dry-run] Would pull ${snippets.length} snippet${snippets.length === 1 ? '' : 's'} to ${path}`);
66
+ if (orphans.length > 0) {
67
+ this.log(`[dry-run] Would remove ${orphans.length} local file${orphans.length === 1 ? '' : 's'} whose snippet no longer exists on WordPress: ${orphans.join(', ')}`);
68
+ }
60
69
  return;
61
70
  }
62
71
  await mkdir(path, { recursive: true });
63
- const pullable = snippets.filter((snippet) => snippet.name.trim());
64
- const skipped = snippets.length - pullable.length;
65
72
  await new Listr(pullable.map((snippet) => ({
66
73
  async task(_ctx, task) {
67
74
  const ext = EXTENSIONS[snippet.type];
@@ -73,9 +80,36 @@ export default class Pull extends LoopressCommand {
73
80
  },
74
81
  title: `Pull ${snippet.name}`,
75
82
  }))).run();
83
+ for (const file of orphans)
84
+ await rm(join(path, file), { force: true });
85
+ if (orphans.length > 0) {
86
+ this.warn(`Removed ${orphans.length} local file${orphans.length === 1 ? '' : 's'} whose snippet no longer exists on WordPress: ${orphans.join(', ')}`);
87
+ }
76
88
  this.log(`Pulled ${pullable.length} snippet${pullable.length === 1 ? '' : 's'} to ${path}`);
77
89
  if (skipped > 0) {
78
90
  this.warn(`${skipped} snippet${skipped === 1 ? '' : 's'} skipped because they have no name`);
79
91
  }
80
92
  }
93
+ // Only ever matches files already following the `<id>-<slug>` convention that `snippet
94
+ // pull`/`push` themselves produce, so a hand-created file without a numeric prefix is
95
+ // never at risk of being picked up here.
96
+ async findOrphanedFiles(path, keepIds) {
97
+ let files;
98
+ try {
99
+ files = await readdir(path);
100
+ }
101
+ catch (error) {
102
+ if (error.code === 'ENOENT')
103
+ return [];
104
+ throw error;
105
+ }
106
+ const knownExtensions = new Set(['json', ...Object.values(EXTENSIONS)]);
107
+ return files.filter((file) => {
108
+ const ext = extname(file).slice(1);
109
+ if (!knownExtensions.has(ext))
110
+ return false;
111
+ const match = /^(\d+)-/.exec(file);
112
+ return match !== null && !keepIds.has(Number(match[1]));
113
+ });
114
+ }
81
115
  }
@@ -79,14 +79,23 @@ export default class Push extends PushCommand {
79
79
  }
80
80
  async loadSnippets(path) {
81
81
  const snippets = [];
82
+ let files;
82
83
  try {
83
- const files = await readdir(path);
84
- for (const file of files) {
85
- const ext = extname(file);
86
- if (!(ext in TYPE_BY_EXTENSION))
87
- continue;
88
- const filePath = join(path, file);
89
- const metaPath = join(path, `${basename(file, ext)}.json`);
84
+ files = await readdir(path);
85
+ }
86
+ catch (error) {
87
+ this.error(`Error loading snippets: ${error.message}`);
88
+ }
89
+ for (const file of files) {
90
+ const ext = extname(file);
91
+ if (!(ext in TYPE_BY_EXTENSION))
92
+ continue;
93
+ const filePath = join(path, file);
94
+ const metaPath = join(path, `${basename(file, ext)}.json`);
95
+ // One snippet's files are read in isolation: a corrupted or hand-broken sidecar
96
+ // (bad JSON, unreadable file, ...) must only skip that snippet, not abort the entire
97
+ // push for every other snippet in the directory.
98
+ try {
90
99
  const content = await readFile(filePath, 'utf8');
91
100
  let id;
92
101
  let name;
@@ -129,9 +138,9 @@ export default class Push extends PushCommand {
129
138
  type: resolvedType,
130
139
  });
131
140
  }
132
- }
133
- catch (error) {
134
- this.error(`Error loading snippets: ${error.message}`);
141
+ catch (error) {
142
+ this.warn(`Skipping "${metaPath}": ${error.message}`);
143
+ }
135
144
  }
136
145
  return snippets;
137
146
  }
@@ -4,6 +4,9 @@ export declare class ProjectConfigManager {
4
4
  constructor(homeDir?: string);
5
5
  createProjectId(name: string): string;
6
6
  ensureConfigDir(): void;
7
+ findProjectByApiId(apiProjectId: string): null | (ProjectConfig & {
8
+ id: string;
9
+ });
7
10
  getConfigFilePath(): string;
8
11
  getCurrentEnv(): EnvironmentConfig | null;
9
12
  getCurrentProject(): null | (ProjectConfig & {
@@ -25,6 +25,18 @@ export class ProjectConfigManager {
25
25
  mkdirSync(dir, { recursive: true });
26
26
  }
27
27
  }
28
+ // Looks up a local project already linked to a given API project, regardless of whether
29
+ // it was "claimed" in the current sync run. Used by `project sync` to avoid minting a new
30
+ // local project every time it pulls an API project whose local link was lost (e.g. after
31
+ // a reset or partial config corruption), which otherwise accumulates duplicate entries.
32
+ findProjectByApiId(apiProjectId) {
33
+ const config = this.readConfig();
34
+ for (const [id, project] of Object.entries(config.projects)) {
35
+ if (project.apiProjectId === apiProjectId)
36
+ return { ...project, id };
37
+ }
38
+ return null;
39
+ }
28
40
  getConfigFilePath() {
29
41
  return join(this.homeDir, '.loopress', 'config.json');
30
42
  }
@@ -48,10 +48,27 @@ export function formatWpError(error, url) {
48
48
  return `Endpoint not found (404) on ${url}. Is the required plugin installed and up to date on the site?`;
49
49
  }
50
50
  if (status !== undefined) {
51
- return `Request failed (${status}) on ${url}.`;
51
+ const reason = extractServerErrorMessage(err.response?.body);
52
+ return reason ? `Request failed (${status}) on ${url}: ${reason}` : `Request failed (${status}) on ${url}.`;
52
53
  }
53
54
  if (err.name === 'TimeoutError') {
54
55
  return `Request timed out after ${REQUEST_TIMEOUT_MS / 1000}s on ${url}. Is the site reachable?`;
55
56
  }
56
57
  return `Request to ${url} failed: ${err.message ?? String(error)}`;
57
58
  }
59
+ // The Loopress plugin's own controllers reply with `{"error": "..."}`; a WP_Error-based
60
+ // core response (e.g. an uncaught fatal formatted by WordPress itself) uses `{"message": "..."}`.
61
+ // Surfacing this is what makes a deliberately clear server-side error (e.g. "Multiple snippet
62
+ // plugins are active...") actually reach the user instead of a bare, unhelpful status code.
63
+ function extractServerErrorMessage(body) {
64
+ if (!body)
65
+ return undefined;
66
+ try {
67
+ const parsed = JSON.parse(body);
68
+ const reason = parsed.error ?? parsed.message;
69
+ return typeof reason === 'string' && reason.trim() ? reason : undefined;
70
+ }
71
+ catch {
72
+ return undefined;
73
+ }
74
+ }
@@ -311,89 +311,140 @@
311
311
  "push.js"
312
312
  ]
313
313
  },
314
- "project:config": {
314
+ "snippet:list": {
315
315
  "aliases": [],
316
316
  "args": {},
317
- "description": "Add or update a WordPress project environment",
317
+ "description": "List snippets from WordPress",
318
318
  "examples": [
319
- "$ lps project config"
319
+ "$ lps snippet list"
320
320
  ],
321
- "flags": {},
321
+ "flags": {
322
+ "json": {
323
+ "char": "j",
324
+ "description": "Output in JSON format",
325
+ "name": "json",
326
+ "allowNo": false,
327
+ "type": "boolean"
328
+ }
329
+ },
322
330
  "hasDynamicHelp": false,
323
331
  "hiddenAliases": [],
324
- "id": "project:config",
332
+ "id": "snippet:list",
325
333
  "pluginAlias": "@loopress/cli",
326
334
  "pluginName": "@loopress/cli",
327
335
  "pluginType": "core",
328
336
  "strict": true,
329
337
  "enableJsonFlag": false,
338
+ "dryRunFlag": {
339
+ "dry-run": {
340
+ "char": "d",
341
+ "description": "Show what would change without making changes",
342
+ "allowNo": false,
343
+ "type": "boolean"
344
+ }
345
+ },
330
346
  "isESM": true,
331
347
  "relativePath": [
332
348
  "dist",
333
349
  "commands",
334
- "project",
335
- "config.js"
350
+ "snippet",
351
+ "list.js"
336
352
  ]
337
353
  },
338
- "project:list": {
354
+ "snippet:pull": {
339
355
  "aliases": [],
340
- "args": {},
341
- "description": "List configured WordPress projects",
356
+ "args": {
357
+ "path": {
358
+ "description": "Path to snippets directory (overrides project config)",
359
+ "name": "path"
360
+ }
361
+ },
362
+ "description": "Pull snippets from WordPress",
342
363
  "examples": [
343
- "$ lps project list"
364
+ "$ lps snippet pull",
365
+ "$ lps snippet pull --path ./snippets"
344
366
  ],
345
- "flags": {},
367
+ "flags": {
368
+ "dry-run": {
369
+ "char": "d",
370
+ "description": "Show what would change without making changes",
371
+ "name": "dry-run",
372
+ "allowNo": false,
373
+ "type": "boolean"
374
+ }
375
+ },
346
376
  "hasDynamicHelp": false,
347
377
  "hiddenAliases": [],
348
- "id": "project:list",
378
+ "id": "snippet:pull",
349
379
  "pluginAlias": "@loopress/cli",
350
380
  "pluginName": "@loopress/cli",
351
381
  "pluginType": "core",
352
382
  "strict": true,
353
383
  "enableJsonFlag": false,
384
+ "dryRunFlag": {
385
+ "dry-run": {
386
+ "char": "d",
387
+ "description": "Show what would change without making changes",
388
+ "allowNo": false,
389
+ "type": "boolean"
390
+ }
391
+ },
354
392
  "isESM": true,
355
393
  "relativePath": [
356
394
  "dist",
357
395
  "commands",
358
- "project",
359
- "list.js"
396
+ "snippet",
397
+ "pull.js"
360
398
  ]
361
399
  },
362
- "project:remove": {
400
+ "snippet:push": {
363
401
  "aliases": [],
364
- "args": {},
365
- "description": "Remove one or more WordPress projects or environments",
402
+ "args": {
403
+ "path": {
404
+ "description": "Path to snippets directory (overrides project config)",
405
+ "name": "path"
406
+ }
407
+ },
408
+ "description": "Push snippets to WordPress. Local snippet files created or updated remotely are renamed on disk to the `<id>-<slug>` convention.",
366
409
  "examples": [
367
- "$ lps project remove"
410
+ "$ lps snippet push",
411
+ "$ lps snippet push --path ./snippets"
368
412
  ],
369
- "flags": {},
413
+ "flags": {
414
+ "dry-run": {
415
+ "char": "d",
416
+ "description": "Show what would change without making changes",
417
+ "name": "dry-run",
418
+ "allowNo": false,
419
+ "type": "boolean"
420
+ }
421
+ },
370
422
  "hasDynamicHelp": false,
371
423
  "hiddenAliases": [],
372
- "id": "project:remove",
424
+ "id": "snippet:push",
373
425
  "pluginAlias": "@loopress/cli",
374
426
  "pluginName": "@loopress/cli",
375
427
  "pluginType": "core",
376
428
  "strict": true,
377
- "enableJsonFlag": false,
378
429
  "isESM": true,
379
430
  "relativePath": [
380
431
  "dist",
381
432
  "commands",
382
- "project",
383
- "remove.js"
433
+ "snippet",
434
+ "push.js"
384
435
  ]
385
436
  },
386
- "project:switch": {
437
+ "project:config": {
387
438
  "aliases": [],
388
439
  "args": {},
389
- "description": "Switch the active project and environment",
440
+ "description": "Add or update a WordPress project environment",
390
441
  "examples": [
391
- "$ lps project switch"
442
+ "$ lps project config"
392
443
  ],
393
444
  "flags": {},
394
445
  "hasDynamicHelp": false,
395
446
  "hiddenAliases": [],
396
- "id": "project:switch",
447
+ "id": "project:config",
397
448
  "pluginAlias": "@loopress/cli",
398
449
  "pluginName": "@loopress/cli",
399
450
  "pluginType": "core",
@@ -404,20 +455,20 @@
404
455
  "dist",
405
456
  "commands",
406
457
  "project",
407
- "switch.js"
458
+ "config.js"
408
459
  ]
409
460
  },
410
- "project:sync": {
461
+ "project:list": {
411
462
  "aliases": [],
412
463
  "args": {},
413
- "description": "Sync locally configured projects and environments with your Loopress account",
464
+ "description": "List configured WordPress projects",
414
465
  "examples": [
415
- "$ lps project sync"
466
+ "$ lps project list"
416
467
  ],
417
468
  "flags": {},
418
469
  "hasDynamicHelp": false,
419
470
  "hiddenAliases": [],
420
- "id": "project:sync",
471
+ "id": "project:list",
421
472
  "pluginAlias": "@loopress/cli",
422
473
  "pluginName": "@loopress/cli",
423
474
  "pluginType": "core",
@@ -428,132 +479,81 @@
428
479
  "dist",
429
480
  "commands",
430
481
  "project",
431
- "sync.js"
482
+ "list.js"
432
483
  ]
433
484
  },
434
- "snippet:list": {
485
+ "project:remove": {
435
486
  "aliases": [],
436
487
  "args": {},
437
- "description": "List snippets from WordPress",
488
+ "description": "Remove one or more WordPress projects or environments",
438
489
  "examples": [
439
- "$ lps snippet list"
490
+ "$ lps project remove"
440
491
  ],
441
- "flags": {
442
- "json": {
443
- "char": "j",
444
- "description": "Output in JSON format",
445
- "name": "json",
446
- "allowNo": false,
447
- "type": "boolean"
448
- }
449
- },
492
+ "flags": {},
450
493
  "hasDynamicHelp": false,
451
494
  "hiddenAliases": [],
452
- "id": "snippet:list",
495
+ "id": "project:remove",
453
496
  "pluginAlias": "@loopress/cli",
454
497
  "pluginName": "@loopress/cli",
455
498
  "pluginType": "core",
456
499
  "strict": true,
457
500
  "enableJsonFlag": false,
458
- "dryRunFlag": {
459
- "dry-run": {
460
- "char": "d",
461
- "description": "Show what would change without making changes",
462
- "allowNo": false,
463
- "type": "boolean"
464
- }
465
- },
466
501
  "isESM": true,
467
502
  "relativePath": [
468
503
  "dist",
469
504
  "commands",
470
- "snippet",
471
- "list.js"
505
+ "project",
506
+ "remove.js"
472
507
  ]
473
508
  },
474
- "snippet:pull": {
509
+ "project:switch": {
475
510
  "aliases": [],
476
- "args": {
477
- "path": {
478
- "description": "Path to snippets directory (overrides project config)",
479
- "name": "path"
480
- }
481
- },
482
- "description": "Pull snippets from WordPress",
511
+ "args": {},
512
+ "description": "Switch the active project and environment",
483
513
  "examples": [
484
- "$ lps snippet pull",
485
- "$ lps snippet pull --path ./snippets"
514
+ "$ lps project switch"
486
515
  ],
487
- "flags": {
488
- "dry-run": {
489
- "char": "d",
490
- "description": "Show what would change without making changes",
491
- "name": "dry-run",
492
- "allowNo": false,
493
- "type": "boolean"
494
- }
495
- },
516
+ "flags": {},
496
517
  "hasDynamicHelp": false,
497
518
  "hiddenAliases": [],
498
- "id": "snippet:pull",
519
+ "id": "project:switch",
499
520
  "pluginAlias": "@loopress/cli",
500
521
  "pluginName": "@loopress/cli",
501
522
  "pluginType": "core",
502
523
  "strict": true,
503
524
  "enableJsonFlag": false,
504
- "dryRunFlag": {
505
- "dry-run": {
506
- "char": "d",
507
- "description": "Show what would change without making changes",
508
- "allowNo": false,
509
- "type": "boolean"
510
- }
511
- },
512
525
  "isESM": true,
513
526
  "relativePath": [
514
527
  "dist",
515
528
  "commands",
516
- "snippet",
517
- "pull.js"
529
+ "project",
530
+ "switch.js"
518
531
  ]
519
532
  },
520
- "snippet:push": {
533
+ "project:sync": {
521
534
  "aliases": [],
522
- "args": {
523
- "path": {
524
- "description": "Path to snippets directory (overrides project config)",
525
- "name": "path"
526
- }
527
- },
528
- "description": "Push snippets to WordPress. Local snippet files created or updated remotely are renamed on disk to the `<id>-<slug>` convention.",
535
+ "args": {},
536
+ "description": "Sync locally configured projects and environments with your Loopress account",
529
537
  "examples": [
530
- "$ lps snippet push",
531
- "$ lps snippet push --path ./snippets"
538
+ "$ lps project sync"
532
539
  ],
533
- "flags": {
534
- "dry-run": {
535
- "char": "d",
536
- "description": "Show what would change without making changes",
537
- "name": "dry-run",
538
- "allowNo": false,
539
- "type": "boolean"
540
- }
541
- },
540
+ "flags": {},
542
541
  "hasDynamicHelp": false,
543
542
  "hiddenAliases": [],
544
- "id": "snippet:push",
543
+ "id": "project:sync",
545
544
  "pluginAlias": "@loopress/cli",
546
545
  "pluginName": "@loopress/cli",
547
546
  "pluginType": "core",
548
547
  "strict": true,
548
+ "enableJsonFlag": false,
549
549
  "isESM": true,
550
550
  "relativePath": [
551
551
  "dist",
552
552
  "commands",
553
- "snippet",
554
- "push.js"
553
+ "project",
554
+ "sync.js"
555
555
  ]
556
556
  }
557
557
  },
558
- "version": "0.11.0"
558
+ "version": "0.12.0"
559
559
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loopress/cli",
3
3
  "description": "CLI tool for syncing WordPress code snippets, plugins, and Composer dependencies via the REST API",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "author": "jean-smaug",
6
6
  "bin": {
7
7
  "loopress": "bin/run.js",