@loopress/cli 0.20.0 → 0.21.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.
Files changed (84) hide show
  1. package/README.md +128 -62
  2. package/dist/commands/acf/list.js +2 -1
  3. package/dist/commands/acf/push.js +1 -1
  4. package/dist/commands/api/list.d.ts +7 -4
  5. package/dist/commands/api/list.js +3 -10
  6. package/dist/commands/api/publish.js +1 -1
  7. package/dist/commands/api/pull.d.ts +8 -1
  8. package/dist/commands/api/pull.js +13 -4
  9. package/dist/commands/api/push.d.ts +7 -1
  10. package/dist/commands/api/push.js +48 -18
  11. package/dist/commands/composer/init.js +6 -6
  12. package/dist/commands/composer/pull.d.ts +7 -1
  13. package/dist/commands/composer/pull.js +41 -4
  14. package/dist/commands/composer/push.d.ts +8 -1
  15. package/dist/commands/composer/push.js +3 -1
  16. package/dist/commands/dev.d.ts +13 -0
  17. package/dist/commands/dev.js +97 -0
  18. package/dist/commands/doctor.js +4 -4
  19. package/dist/commands/init.js +7 -7
  20. package/dist/commands/login.js +3 -3
  21. package/dist/commands/page/list.d.ts +2 -4
  22. package/dist/commands/page/list.js +3 -10
  23. package/dist/commands/page/pull.d.ts +13 -1
  24. package/dist/commands/page/pull.js +5 -2
  25. package/dist/commands/page/push.d.ts +11 -1
  26. package/dist/commands/page/push.js +25 -13
  27. package/dist/commands/plugin/add.js +3 -3
  28. package/dist/commands/plugin/pull.d.ts +7 -1
  29. package/dist/commands/plugin/pull.js +3 -1
  30. package/dist/commands/plugin/push.d.ts +9 -1
  31. package/dist/commands/plugin/push.js +13 -9
  32. package/dist/commands/project/config.js +8 -8
  33. package/dist/commands/project/list.js +2 -2
  34. package/dist/commands/project/push.js +5 -5
  35. package/dist/commands/project/switch.js +2 -2
  36. package/dist/commands/snippet/list.d.ts +3 -4
  37. package/dist/commands/snippet/list.js +3 -10
  38. package/dist/commands/snippet/pull.d.ts +13 -1
  39. package/dist/commands/snippet/pull.js +5 -2
  40. package/dist/commands/snippet/push.d.ts +11 -1
  41. package/dist/commands/snippet/push.js +15 -5
  42. package/dist/commands/status.d.ts +13 -1
  43. package/dist/commands/status.js +25 -18
  44. package/dist/config/auth.manager.d.ts +2 -2
  45. package/dist/config/project-config.manager.d.ts +1 -1
  46. package/dist/config/project-config.manager.js +5 -6
  47. package/dist/lib/base.d.ts +2 -2
  48. package/dist/lib/base.js +3 -2
  49. package/dist/lib/bootstrap-full-install.d.ts +1 -1
  50. package/dist/lib/dev-watch.d.ts +16 -0
  51. package/dist/lib/dev-watch.js +71 -0
  52. package/dist/lib/find-orphaned-files.d.ts +3 -2
  53. package/dist/lib/find-orphaned-files.js +40 -4
  54. package/dist/lib/github-release.js +1 -1
  55. package/dist/lib/interactive.js +1 -1
  56. package/dist/lib/load-files.d.ts +1 -0
  57. package/dist/lib/load-files.js +40 -11
  58. package/dist/lib/load-snippets.d.ts +1 -1
  59. package/dist/lib/load-snippets.js +11 -9
  60. package/dist/lib/local-callback-server.js +8 -6
  61. package/dist/lib/plugin-detection.d.ts +1 -1
  62. package/dist/lib/push-command.js +2 -2
  63. package/dist/lib/rotate-app-password.d.ts +1 -1
  64. package/dist/lib/rotate-app-password.js +1 -1
  65. package/dist/lib/temp-admin.d.ts +3 -3
  66. package/dist/lib/temp-admin.js +2 -2
  67. package/dist/lib/wp-authorize-flow.js +5 -1
  68. package/dist/lib/wp-client.d.ts +2 -2
  69. package/dist/lib/wp-client.js +11 -1
  70. package/dist/lib/wp-site-diagnostic.d.ts +9 -2
  71. package/dist/lib/wp-site-diagnostic.js +13 -23
  72. package/dist/types/plugin.d.ts +4 -4
  73. package/dist/types/snippet.d.ts +3 -3
  74. package/dist/utils/composer.d.ts +2 -2
  75. package/dist/utils/form-format.js +1 -1
  76. package/dist/utils/loopress-config.d.ts +1 -1
  77. package/dist/utils/page-format.js +2 -2
  78. package/dist/utils/plugins.d.ts +5 -5
  79. package/dist/utils/plugins.js +3 -3
  80. package/dist/utils/seo-format.d.ts +6 -6
  81. package/dist/utils/snippet-format.d.ts +2 -2
  82. package/dist/utils/snippet-format.js +10 -5
  83. package/oclif.manifest.json +153 -31
  84. package/package.json +9 -8
@@ -1,26 +1,19 @@
1
- import { Flags } from '@oclif/core';
2
1
  import { LoopressCommand } from '../../lib/base.js';
3
2
  import { getPageId, getPageTitle, PAGE_ENDPOINT, PAGE_LIST_QUERY } from '../../utils/page-format.js';
4
3
  export default class List extends LoopressCommand {
5
4
  static description = 'List pages from WordPress';
5
+ static enableJsonFlag = true;
6
6
  static examples = ['$ lps page list'];
7
- static flags = {
8
- json: Flags.boolean({ char: 'j', description: 'Output in JSON format' }),
9
- };
10
7
  async run() {
11
- const { flags } = await this.parse(List);
12
8
  const pages = await this.wp.get(`${PAGE_ENDPOINT}?${PAGE_LIST_QUERY}`);
13
- if (flags.json) {
14
- this.log(JSON.stringify(pages, null, 2));
15
- return;
16
- }
17
9
  this.log(`Pages (${pages.length}):`);
18
10
  if (pages.length === 0) {
19
11
  this.log(' (none)');
20
- return;
12
+ return pages;
21
13
  }
22
14
  for (const page of pages) {
23
15
  this.log(` ${getPageId(page) ?? '(no id)'}. ${getPageTitle(page)}`);
24
16
  }
17
+ return pages;
25
18
  }
26
19
  }
@@ -1,13 +1,25 @@
1
1
  import { LoopressCommand } from '../../lib/base.js';
2
+ type PulledPage = {
3
+ id: number;
4
+ title: string;
5
+ };
6
+ type PullResult = {
7
+ orphans: string[];
8
+ pulled: PulledPage[];
9
+ skipped: number;
10
+ status: 'dry-run' | 'success';
11
+ };
2
12
  export default class Pull extends LoopressCommand {
3
13
  static args: {
4
14
  path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
15
  };
6
16
  static description: string;
17
+ static enableJsonFlag: boolean;
7
18
  static examples: string[];
8
19
  static flags: {
9
20
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
21
  'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
22
  };
12
- run(): Promise<void>;
23
+ run(): Promise<PullResult>;
13
24
  }
25
+ export {};
@@ -10,6 +10,7 @@ export default class Pull extends LoopressCommand {
10
10
  path: Args.string({ description: 'Path to pages directory (overrides project config)' }),
11
11
  };
12
12
  static description = 'Pull pages from WordPress';
13
+ static enableJsonFlag = true;
13
14
  static examples = ['$ lps page pull'];
14
15
  static flags = {
15
16
  ...LoopressCommand.dryRunFlag,
@@ -32,12 +33,13 @@ export default class Pull extends LoopressCommand {
32
33
  extensions: ['.json', '.html'],
33
34
  key: numericPrefixKey,
34
35
  });
36
+ const pulled = withId.map((page) => ({ id: getPageId(page), title: getPageTitle(page) }));
35
37
  if (this.dryRun) {
36
38
  this.log(`[dry-run] Would pull ${withId.length} page${withId.length === 1 ? '' : 's'} to ${path}`);
37
39
  if (orphans.length > 0) {
38
40
  this.log(`[dry-run] Would remove ${orphans.length} local file${orphans.length === 1 ? '' : 's'} in ${path} no longer present on WordPress: ${orphans.join(', ')}`);
39
41
  }
40
- return;
42
+ return { orphans, pulled, skipped, status: 'dry-run' };
41
43
  }
42
44
  if (withId.length > 0)
43
45
  await mkdir(path, { recursive: true });
@@ -55,11 +57,12 @@ export default class Pull extends LoopressCommand {
55
57
  },
56
58
  title: `Pull ${title}`,
57
59
  };
58
- })).run();
60
+ }), { renderer: this.jsonEnabled() ? 'silent' : 'default' }).run();
59
61
  await this.removeOrphanedFiles(path, orphans, `in ${path} no longer present on WordPress`);
60
62
  this.log(`Pulled ${withId.length} page${withId.length === 1 ? '' : 's'} to ${path}`);
61
63
  if (skipped > 0) {
62
64
  this.warn(`${skipped} page${skipped === 1 ? '' : 's'} skipped because they have no id`);
63
65
  }
66
+ return { orphans, pulled, skipped, status: 'success' };
64
67
  }
65
68
  }
@@ -1,16 +1,26 @@
1
1
  import { PushCommand } from '../../lib/push-command.js';
2
+ type PushedPage = {
3
+ id: null | number;
4
+ title: string;
5
+ };
6
+ type PushResult = {
7
+ pushed: PushedPage[];
8
+ status: 'dry-run' | 'success';
9
+ };
2
10
  export default class Push extends PushCommand {
3
11
  static args: {
4
12
  path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
13
  };
6
14
  static description: string;
15
+ static enableJsonFlag: boolean;
7
16
  static examples: string[];
8
17
  static flags: {
9
18
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
19
  'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
20
  };
12
- run(): Promise<void>;
21
+ run(): Promise<PushResult>;
13
22
  private ensureCanonicalFilename;
14
23
  private loadFiles;
15
24
  private pushPage;
16
25
  }
26
+ export {};
@@ -10,6 +10,7 @@ export default class Push extends PushCommand {
10
10
  path: Args.string({ description: 'Path to pages directory (overrides project config)' }),
11
11
  };
12
12
  static description = 'Push pages to WordPress. Local files created or updated remotely are renamed on disk to the `<id>-<slug>` convention.';
13
+ static enableJsonFlag = true;
13
14
  static examples = ['$ lps page push'];
14
15
  static flags = {
15
16
  ...PushCommand.dryRunFlag,
@@ -23,17 +24,22 @@ export default class Push extends PushCommand {
23
24
  this.log(`Pages path: ${path}`);
24
25
  const pages = await this.loadFiles(path);
25
26
  this.log(`Found ${pages.length} page${pages.length === 1 ? '' : 's'} to push`);
27
+ const pushed = [];
26
28
  await new Listr(pages.map((page) => ({
27
- task: async (_ctx, task) => this.pushPage(page, task),
29
+ task: async (_ctx, task) => {
30
+ const id = await this.pushPage(page, task);
31
+ pushed.push({ id, title: getPageTitle(page.meta) });
32
+ },
28
33
  title: `Push ${getPageTitle(page.meta)}`,
29
- })), { concurrent: false, exitOnError: false }).run();
34
+ })), { concurrent: false, exitOnError: false, renderer: this.jsonEnabled() ? 'silent' : 'default' }).run();
30
35
  if (this.failedCount > 0) {
31
36
  this.error(`${this.failedCount} page${this.failedCount === 1 ? '' : 's'} failed to push.`);
32
37
  }
33
38
  if (this.dryRun)
34
- return;
39
+ return { pushed, status: 'dry-run' };
35
40
  await this.recordSuccess();
36
41
  this.log('All pages pushed.');
42
+ return { pushed, status: 'success' };
37
43
  }
38
44
  // Renames the local file pair to the `<id>-<slug>` convention used by `page pull` whenever it
39
45
  // doesn't already match, same principle as ensureCanonicalFilename in commands/snippet/push.ts:
@@ -108,16 +114,16 @@ export default class Push extends PushCommand {
108
114
  if (this.dryRun) {
109
115
  if (task)
110
116
  task.output = `[dry-run] Would push: ${title}`;
111
- return;
117
+ return getPageId(page.meta);
112
118
  }
113
119
  try {
114
- const id = getPageId(page.meta);
120
+ let id = getPageId(page.meta);
115
121
  const payload = { ...page.meta, content: page.content };
116
122
  if (id === null) {
117
123
  const created = await this.wp.post(PAGE_ENDPOINT, payload);
118
- const newId = getPageId(created);
119
- if (newId !== null)
120
- await this.ensureCanonicalFilename(page, newId, title);
124
+ id = getPageId(created);
125
+ if (id !== null)
126
+ await this.ensureCanonicalFilename(page, id, title);
121
127
  }
122
128
  else {
123
129
  try {
@@ -126,17 +132,23 @@ export default class Push extends PushCommand {
126
132
  }
127
133
  catch (error) {
128
134
  // The id recorded locally doesn't exist on this site (e.g. a fresh install): create
129
- // it instead of failing, and adopt whatever id the site assigns.
135
+ // it instead of failing, and adopt whatever id the site assigns. WordPress core
136
+ // rejects any POST that still carries a (now stale) `id` field with a 400 "Cannot
137
+ // create existing post", regardless of whether that id actually exists, so it must
138
+ // be stripped before falling back to create.
130
139
  if (!isNotFoundError(error))
131
140
  throw error;
132
- const created = await this.wp.post(PAGE_ENDPOINT, payload);
133
- const newId = getPageId(created);
134
- if (newId !== null)
135
- await this.ensureCanonicalFilename(page, newId, title);
141
+ const newPayload = { ...payload };
142
+ delete newPayload.id;
143
+ const created = await this.wp.post(PAGE_ENDPOINT, newPayload);
144
+ id = getPageId(created);
145
+ if (id !== null)
146
+ await this.ensureCanonicalFilename(page, id, title);
136
147
  }
137
148
  }
138
149
  if (task)
139
150
  task.output = `Pushed: ${title}`;
151
+ return id;
140
152
  }
141
153
  catch (error) {
142
154
  this.reportTaskFailure(`Failed to push ${title}: ${error.message}`, error, task);
@@ -18,15 +18,15 @@ export default class Add extends LoopressCommand {
18
18
  this.log(`${slug} is already in loopress.json, nothing to do.`);
19
19
  return;
20
20
  }
21
- const updated = existing[slug] !== undefined;
21
+ const isUpdated = existing[slug] !== undefined;
22
22
  if (this.dryRun) {
23
- this.log(`[dry-run] Would ${updated ? 'update' : 'add'} ${slug} in loopress.json`);
23
+ this.log(`[dry-run] Would ${isUpdated ? 'update' : 'add'} ${slug} in loopress.json`);
24
24
  return;
25
25
  }
26
26
  await writeLocalConfig({
27
27
  ...this.localConfig,
28
28
  plugins: { ...existing, [slug]: 'latest' },
29
29
  });
30
- this.log(`${updated ? 'Updated' : 'Added'} ${slug}`);
30
+ this.log(`${isUpdated ? 'Updated' : 'Added'} ${slug}`);
31
31
  }
32
32
  }
@@ -1,9 +1,15 @@
1
1
  import { LoopressCommand } from '../../lib/base.js';
2
+ import { type MergeResult } from '../../utils/plugins.js';
3
+ type PullResult = MergeResult & {
4
+ status: 'dry-run' | 'success';
5
+ };
2
6
  export default class Pull extends LoopressCommand {
3
7
  static description: string;
8
+ static enableJsonFlag: boolean;
4
9
  static examples: string[];
5
10
  static flags: {
6
11
  'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
12
  };
8
- run(): Promise<void>;
13
+ run(): Promise<PullResult>;
9
14
  }
15
+ export {};
@@ -4,6 +4,7 @@ import { writeLocalConfig } from '../../utils/loopress-config.js';
4
4
  import { mergePluginManifest, parseInstalledPlugins } from '../../utils/plugins.js';
5
5
  export default class Pull extends LoopressCommand {
6
6
  static description = 'Pull installed plugins from WordPress into loopress.json';
7
+ static enableJsonFlag = true;
7
8
  static examples = ['$ lps plugin pull', '$ lps plugin pull --dry-run'];
8
9
  static flags = {
9
10
  ...LoopressCommand.dryRunFlag,
@@ -31,7 +32,7 @@ export default class Pull extends LoopressCommand {
31
32
  for (const u of updated)
32
33
  this.log(` ~ ${u.slug} (${u.from} → ${u.to})`);
33
34
  }
34
- return;
35
+ return { added, merged, status: 'dry-run', updated };
35
36
  }
36
37
  await writeLocalConfig({ ...this.localConfig, plugins: merged });
37
38
  this.log(`Wrote ${Object.keys(merged).length} plugins to loopress.json`);
@@ -39,5 +40,6 @@ export default class Pull extends LoopressCommand {
39
40
  this.log(` + Added: ${added.join(', ')}`);
40
41
  for (const u of updated)
41
42
  this.log(` ~ Updated: ${u.slug} ${u.from} → ${u.to}`);
43
+ return { added, merged, status: 'success', updated };
42
44
  }
43
45
  }
@@ -1,13 +1,21 @@
1
1
  import { PushCommand } from '../../lib/push-command.js';
2
+ type PushResult = {
3
+ activated: string[];
4
+ installed: string[];
5
+ skippedComposerManaged: string[];
6
+ status: 'dry-run' | 'in-sync' | 'success';
7
+ };
2
8
  export default class Push extends PushCommand {
3
9
  static description: string;
10
+ static enableJsonFlag: boolean;
4
11
  static examples: string[];
5
12
  static flags: {
6
13
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
14
  'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
15
  };
9
- run(): Promise<void>;
16
+ run(): Promise<PushResult>;
10
17
  private activatePlugin;
11
18
  private installPlugin;
12
19
  private performPluginAction;
13
20
  }
21
+ export {};
@@ -4,6 +4,7 @@ import { getComposerManagedSlugs, readComposerJson } from '../../utils/composer.
4
4
  import { diffPlugins, parseInstalledPlugins } from '../../utils/plugins.js';
5
5
  export default class Push extends PushCommand {
6
6
  static description = 'Push plugins to WordPress to match loopress.json';
7
+ static enableJsonFlag = true;
7
8
  static examples = ['$ lps plugin push', '$ lps plugin push --dry-run'];
8
9
  static flags = {
9
10
  ...PushCommand.dryRunFlag,
@@ -18,9 +19,9 @@ export default class Push extends PushCommand {
18
19
  const composerJson = await readComposerJson();
19
20
  const composerSlugs = composerJson ? getComposerManagedSlugs(composerJson) : [];
20
21
  const filteredManifest = Object.fromEntries(Object.entries(manifest).filter(([slug]) => !composerSlugs.includes(slug)));
21
- const skipped = composerSlugs.filter((slug) => slug in manifest);
22
- if (skipped.length > 0) {
23
- this.log(`Skipping ${skipped.length} Composer-managed ${skipped.length === 1 ? 'plugin' : 'plugins'}: ${skipped.join(', ')}`);
22
+ const skippedComposerManaged = composerSlugs.filter((slug) => Object.hasOwn(manifest, slug));
23
+ if (skippedComposerManaged.length > 0) {
24
+ this.log(`Skipping ${skippedComposerManaged.length} Composer-managed ${skippedComposerManaged.length === 1 ? 'plugin' : 'plugins'}: ${skippedComposerManaged.join(', ')}`);
24
25
  this.log('Run `lps composer push` to deploy them.');
25
26
  }
26
27
  this.log(`Pushing plugins to ${url}`);
@@ -29,7 +30,7 @@ export default class Push extends PushCommand {
29
30
  const { toActivate, toInstall } = diffPlugins(filteredManifest, installed);
30
31
  if (toInstall.length === 0 && toActivate.length === 0) {
31
32
  this.log('Everything is already in sync.');
32
- return;
33
+ return { activated: [], installed: [], skippedComposerManaged, status: 'in-sync' };
33
34
  }
34
35
  if (toInstall.length > 0) {
35
36
  this.log(`\nTo install (${toInstall.length}):`);
@@ -41,32 +42,35 @@ export default class Push extends PushCommand {
41
42
  for (const a of toActivate)
42
43
  this.log(` ↑ ${a.slug}`);
43
44
  }
45
+ const installedSlugs = toInstall.map((a) => a.slug);
46
+ const activatedSlugs = toActivate.map((a) => a.slug);
44
47
  if (this.dryRun)
45
- return;
48
+ return { activated: activatedSlugs, installed: installedSlugs, skippedComposerManaged, status: 'dry-run' };
46
49
  // Installing with `status: active` activates in the same call, so installs never need a
47
50
  // separate activation step the way the old custom endpoint's two-step flow did.
48
51
  if (toInstall.length > 0) {
49
52
  await new Listr(toInstall.map((action) => ({
50
53
  task: async (_ctx, task) => this.installPlugin(action.slug, task),
51
54
  title: `Install ${action.slug}`,
52
- })), { concurrent: false, exitOnError: false }).run();
55
+ })), { concurrent: false, exitOnError: false, renderer: this.jsonEnabled() ? 'silent' : 'default' }).run();
53
56
  }
54
57
  if (toActivate.length > 0) {
55
58
  await new Listr(toActivate.map((action) => ({
56
59
  task: async (_ctx, task) => this.activatePlugin(action.file, action.slug, task),
57
60
  title: `Activate ${action.slug}`,
58
- })), { concurrent: false, exitOnError: false }).run();
61
+ })), { concurrent: false, exitOnError: false, renderer: this.jsonEnabled() ? 'silent' : 'default' }).run();
59
62
  }
60
63
  if (this.failedCount > 0) {
61
64
  this.error(`${this.failedCount} plugin${this.failedCount === 1 ? '' : 's'} failed to install or activate.`);
62
65
  }
63
66
  await this.recordSuccess();
67
+ return { activated: activatedSlugs, installed: installedSlugs, skippedComposerManaged, status: 'success' };
64
68
  }
65
69
  async activatePlugin(file, slug, task) {
66
- await this.performPluginAction('activate', slug, () => this.wp.put(`wp/v2/plugins/${file}`, { status: 'active' }), task);
70
+ await this.performPluginAction('activate', slug, async () => this.wp.put(`wp/v2/plugins/${file}`, { status: 'active' }), task);
67
71
  }
68
72
  async installPlugin(slug, task) {
69
- await this.performPluginAction('install', slug, () => this.wp.post('wp/v2/plugins', { slug, status: 'active' }), task);
73
+ await this.performPluginAction('install', slug, async () => this.wp.post('wp/v2/plugins', { slug, status: 'active' }), task);
70
74
  }
71
75
  async performPluginAction(verb, slug, request, task) {
72
76
  try {
@@ -36,11 +36,11 @@ export default class Config extends Command {
36
36
  : envChoice;
37
37
  const existingEnv = configManager.getEnvironment(projectId, envName);
38
38
  if (existingEnv) {
39
- const overwrite = await confirm({
39
+ const isOverwrite = await confirm({
40
40
  default: false,
41
41
  message: `"${projectName}/${envName}" already exists. Overwrite?`,
42
42
  });
43
- if (!overwrite) {
43
+ if (!isOverwrite) {
44
44
  this.log('Aborted.');
45
45
  return;
46
46
  }
@@ -92,17 +92,17 @@ export default class Config extends Command {
92
92
  // A detection failure (network hiccup, plugins REST disabled) is treated as "assume it's
93
93
  // there" rather than surprising the user with an account-creating prompt they didn't ask
94
94
  // for; `lps project config` can simply be re-run if it genuinely isn't installed.
95
- const alreadyActive = await isLoopressFullActive(wp).catch(() => true);
96
- if (alreadyActive)
95
+ const isAlreadyActive = await isLoopressFullActive(wp).catch(() => true);
96
+ if (isAlreadyActive)
97
97
  return;
98
- const proceed = await confirm({
98
+ const isProceed = await confirm({
99
99
  default: true,
100
100
  message: 'Loopress Full was not detected on this site. Install it now? (creates a temporary admin account, removed automatically afterward)',
101
101
  });
102
- if (!proceed)
102
+ if (!isProceed)
103
103
  return;
104
104
  try {
105
- await bootstrapLoopressFull(wp, url, (message) => this.log(message));
105
+ await bootstrapLoopressFull(wp, url, (message) => { this.log(message); });
106
106
  }
107
107
  catch (error) {
108
108
  this.warn(error.message);
@@ -137,7 +137,7 @@ export default class Config extends Command {
137
137
  return this.promptManualCredentials();
138
138
  }
139
139
  try {
140
- const { password, userLogin } = await authorizeWithBrowser(url, (message) => this.log(message));
140
+ const { password, userLogin } = await authorizeWithBrowser(url, (message) => { this.log(message); });
141
141
  return { appPassword: password, user: userLogin };
142
142
  }
143
143
  catch (error) {
@@ -18,8 +18,8 @@ export default class List extends Command {
18
18
  const currentTag = project.isCurrent ? ` ${c('green', '[current]')}` : '';
19
19
  this.log(`${marker} ${name}${currentTag}`);
20
20
  for (const env of envs) {
21
- const envMarker = env.isCurrent ? c('cyan', '·') : c('dim', '·');
22
- const envName = env.isCurrent ? c('cyan', env.name.padEnd(15)) : c('dim', env.name.padEnd(15));
21
+ const envMarker = c(env.isCurrent ? 'cyan' : 'dim', '·');
22
+ const envName = c(env.isCurrent ? 'cyan' : 'dim', env.name.padEnd(15));
23
23
  const envUrl = c('dim', env.url);
24
24
  const activeTag = env.isCurrent ? ` ${c('cyan', '←')}` : '';
25
25
  this.log(` ${envMarker} ${envName} ${envUrl}${activeTag}`);
@@ -16,7 +16,7 @@ export default class Push extends Command {
16
16
  yes = false;
17
17
  async run() {
18
18
  const { flags } = await this.parse(Push);
19
- this.yes = Boolean(flags.yes);
19
+ this.yes = flags.yes;
20
20
  const token = authManager.getAuth()?.token;
21
21
  if (!token) {
22
22
  this.error('Not logged in. Run `lps login` first.');
@@ -158,8 +158,8 @@ export default class Push extends Command {
158
158
  }
159
159
  const match = apiProject?.environments.find((candidate) => candidate.name === env.name && !claimedEnvironmentIds.has(candidate.id));
160
160
  if (match) {
161
- const link = await this.confirmLink(`Environment "${env.name}" already exists on "${apiProject?.name}". Link to it instead of creating a new one?`);
162
- if (link) {
161
+ const isLink = await this.confirmLink(`Environment "${env.name}" already exists on "${apiProject?.name}". Link to it instead of creating a new one?`);
162
+ if (isLink) {
163
163
  claimedEnvironmentIds.add(match.id);
164
164
  return { action: 'link', apiEnvironmentId: match.id, env, projectId };
165
165
  }
@@ -174,8 +174,8 @@ export default class Push extends Command {
174
174
  const slug = toSlug(project.name);
175
175
  const match = apiProjects.find((candidate) => candidate.slug === slug && !claimedProjectIds.has(candidate.id));
176
176
  if (match) {
177
- const link = await this.confirmLink(`A project named "${project.name}" already exists on your account. Link to it instead of creating a new one?`);
178
- if (link) {
177
+ const isLink = await this.confirmLink(`A project named "${project.name}" already exists on your account. Link to it instead of creating a new one?`);
178
+ if (isLink) {
179
179
  claimedProjectIds.add(match.id);
180
180
  return { action: 'link', apiProjectId: match.id, project };
181
181
  }
@@ -28,7 +28,7 @@ export default class Switch extends Command {
28
28
  return { envName: env.name, projectId: project.id, projectName: project.name };
29
29
  }
30
30
  const choices = groups.flatMap(({ envs, project }) => [
31
- new Separator(project.isCurrent ? c('green', `─── ${project.name} ───`) : c('dim', `─── ${project.name} ───`)),
31
+ new Separator(c(project.isCurrent ? 'green' : 'dim', `─── ${project.name} ───`)),
32
32
  ...envs.map((env) => ({
33
33
  name: `${env.name.padEnd(20)} ${env.url}${env.isCurrent ? ' [current]' : ''}`,
34
34
  value: `${project.id}::${env.name}`,
@@ -40,7 +40,7 @@ export default class Switch extends Command {
40
40
  default: current && `${current.project.id}::${current.env.name}`,
41
41
  message: 'Select project / environment',
42
42
  });
43
- const [projectId, envName] = chosen.split('::');
43
+ const [projectId, envName] = chosen.split('::', 2);
44
44
  const { project } = groups.find((group) => group.project.id === projectId);
45
45
  return { envName, projectId, projectName: project.name };
46
46
  }
@@ -1,9 +1,8 @@
1
1
  import { LoopressCommand } from '../../lib/base.js';
2
+ import { type NormalizedSnippet } from '../../utils/snippet-format.js';
2
3
  export default class List extends LoopressCommand {
3
4
  static description: string;
5
+ static enableJsonFlag: boolean;
4
6
  static examples: string[];
5
- static flags: {
6
- json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
- };
8
- run(): Promise<void>;
7
+ run(): Promise<NormalizedSnippet[]>;
9
8
  }
@@ -1,23 +1,15 @@
1
- import { Flags } from '@oclif/core';
2
1
  import { LoopressCommand } from '../../lib/base.js';
3
2
  import { normalizeSnippet, SNIPPETS_ENDPOINT } from '../../utils/snippet-format.js';
4
3
  export default class List extends LoopressCommand {
5
4
  static description = 'List snippets from WordPress';
5
+ static enableJsonFlag = true;
6
6
  static examples = ['$ lps snippet list'];
7
- static flags = {
8
- json: Flags.boolean({ char: 'j', description: 'Output in JSON format' }),
9
- };
10
7
  async run() {
11
- const { flags } = await this.parse(List);
12
8
  const remoteList = await this.wp.get(SNIPPETS_ENDPOINT);
13
9
  const snippets = remoteList.map((r) => normalizeSnippet(r));
14
- if (flags.json) {
15
- this.log(JSON.stringify(snippets, null, 2));
16
- return;
17
- }
18
10
  if (snippets.length === 0) {
19
11
  this.log('No snippets found');
20
- return;
12
+ return snippets;
21
13
  }
22
14
  this.log(`Found ${snippets.length} snippet${snippets.length === 1 ? '' : 's'}:`);
23
15
  this.log('');
@@ -32,5 +24,6 @@ export default class List extends LoopressCommand {
32
24
  }
33
25
  this.log('');
34
26
  }
27
+ return snippets;
35
28
  }
36
29
  }
@@ -1,13 +1,25 @@
1
1
  import { LoopressCommand } from '../../lib/base.js';
2
+ type PulledSnippet = {
3
+ id: number;
4
+ name: string;
5
+ };
6
+ type PullResult = {
7
+ orphans: string[];
8
+ pulled: PulledSnippet[];
9
+ skipped: number;
10
+ status: 'dry-run' | 'success';
11
+ };
2
12
  export default class Pull extends LoopressCommand {
3
13
  static args: {
4
14
  path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
15
  };
6
16
  static description: string;
17
+ static enableJsonFlag: boolean;
7
18
  static examples: string[];
8
19
  static flags: {
9
20
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
21
  'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
22
  };
12
- run(): Promise<void>;
23
+ run(): Promise<PullResult>;
13
24
  }
25
+ export {};
@@ -18,6 +18,7 @@ export default class Pull extends LoopressCommand {
18
18
  path: Args.string({ description: 'Path to snippets directory (overrides project config)' }),
19
19
  };
20
20
  static description = 'Pull snippets from WordPress';
21
+ static enableJsonFlag = true;
21
22
  static examples = ['$ lps snippet pull', '$ lps snippet pull --path ./snippets'];
22
23
  static flags = {
23
24
  ...LoopressCommand.dryRunFlag,
@@ -40,12 +41,13 @@ export default class Pull extends LoopressCommand {
40
41
  extensions: ['.json', ...Object.values(EXTENSIONS).map((ext) => `.${ext}`)],
41
42
  key: numericPrefixKey,
42
43
  });
44
+ const pulled = pullable.map((snippet) => ({ id: snippet.id, name: snippet.name }));
43
45
  if (this.dryRun) {
44
46
  this.log(`[dry-run] Would pull ${snippets.length} snippet${snippets.length === 1 ? '' : 's'} to ${path}`);
45
47
  if (orphans.length > 0) {
46
48
  this.log(`[dry-run] Would remove ${orphans.length} local file${orphans.length === 1 ? '' : 's'} whose snippet no longer exists on WordPress: ${orphans.join(', ')}`);
47
49
  }
48
- return;
50
+ return { orphans, pulled, skipped, status: 'dry-run' };
49
51
  }
50
52
  await mkdir(path, { recursive: true });
51
53
  await new Listr(pullable.map((snippet) => ({
@@ -57,11 +59,12 @@ export default class Pull extends LoopressCommand {
57
59
  task.output = `Pulled: ${snippet.name}`;
58
60
  },
59
61
  title: `Pull ${snippet.name}`,
60
- }))).run();
62
+ })), { renderer: this.jsonEnabled() ? 'silent' : 'default' }).run();
61
63
  await this.removeOrphanedFiles(path, orphans, 'whose snippet no longer exists on WordPress');
62
64
  this.log(`Pulled ${pullable.length} snippet${pullable.length === 1 ? '' : 's'} to ${path}`);
63
65
  if (skipped > 0) {
64
66
  this.warn(`${skipped} snippet${skipped === 1 ? '' : 's'} skipped because they have no name`);
65
67
  }
68
+ return { orphans, pulled, skipped, status: 'success' };
66
69
  }
67
70
  }
@@ -1,17 +1,27 @@
1
1
  import { PushCommand } from '../../lib/push-command.js';
2
+ type PushedSnippet = {
3
+ id?: number;
4
+ name: string;
5
+ };
6
+ type PushResult = {
7
+ pushed: PushedSnippet[];
8
+ status: 'dry-run' | 'success';
9
+ };
2
10
  export default class Push extends PushCommand {
3
11
  static args: {
4
12
  path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
13
  };
6
14
  static description: string;
15
+ static enableJsonFlag: boolean;
7
16
  static examples: string[];
8
17
  static flags: {
9
18
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
19
  'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
20
  };
12
- run(): Promise<void>;
21
+ run(): Promise<PushResult>;
13
22
  private ensureCanonicalFilename;
14
23
  private loadSnippets;
15
24
  private pushSnippet;
16
25
  private toPayload;
17
26
  }
27
+ export {};