@loopress/cli 0.29.0 → 0.30.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 (132) hide show
  1. package/README.md +1301 -345
  2. package/dist/commands/cpt/diff.js +5 -0
  3. package/dist/commands/cpt/list.d.ts +2 -0
  4. package/dist/commands/cpt/list.js +4 -0
  5. package/dist/commands/cpt/pull.d.ts +2 -0
  6. package/dist/commands/cpt/pull.js +4 -0
  7. package/dist/commands/cpt/push.d.ts +2 -0
  8. package/dist/commands/cpt/push.js +4 -0
  9. package/dist/commands/cpt/rm.d.ts +2 -0
  10. package/dist/commands/cpt/rm.js +3 -0
  11. package/dist/commands/cpt/rollback.js +5 -0
  12. package/dist/commands/dev.js +14 -4
  13. package/dist/commands/diff.js +1 -1
  14. package/dist/commands/doctor.d.ts +1 -0
  15. package/dist/commands/doctor.js +30 -2
  16. package/dist/commands/meta/diff.d.ts +2 -0
  17. package/dist/commands/meta/diff.js +5 -0
  18. package/dist/commands/meta/list.d.ts +19 -0
  19. package/dist/commands/meta/list.js +21 -0
  20. package/dist/commands/meta/pull.d.ts +2 -0
  21. package/dist/commands/meta/pull.js +4 -0
  22. package/dist/commands/meta/push.d.ts +2 -0
  23. package/dist/commands/meta/push.js +4 -0
  24. package/dist/commands/meta/rm.d.ts +2 -0
  25. package/dist/commands/meta/rm.js +3 -0
  26. package/dist/commands/meta/rollback.d.ts +2 -0
  27. package/dist/commands/meta/rollback.js +5 -0
  28. package/dist/commands/option/list.js +2 -8
  29. package/dist/commands/pattern/diff.d.ts +2 -0
  30. package/dist/commands/pattern/diff.js +5 -0
  31. package/dist/commands/pattern/list.d.ts +2 -0
  32. package/dist/commands/pattern/list.js +3 -0
  33. package/dist/commands/pattern/pull.d.ts +2 -0
  34. package/dist/commands/pattern/pull.js +4 -0
  35. package/dist/commands/pattern/push.d.ts +2 -0
  36. package/dist/commands/pattern/push.js +4 -0
  37. package/dist/commands/pattern/rm.d.ts +2 -0
  38. package/dist/commands/pattern/rm.js +3 -0
  39. package/dist/commands/pattern/rollback.d.ts +2 -0
  40. package/dist/commands/pattern/rollback.js +5 -0
  41. package/dist/commands/plugin/audit.js +2 -1
  42. package/dist/commands/project/config.d.ts +1 -0
  43. package/dist/commands/project/config.js +20 -1
  44. package/dist/commands/project/list.d.ts +1 -1
  45. package/dist/commands/project/list.js +6 -6
  46. package/dist/commands/project/pull.js +11 -8
  47. package/dist/commands/project/push.js +23 -18
  48. package/dist/commands/project/remove.js +1 -1
  49. package/dist/commands/project/switch.d.ts +0 -1
  50. package/dist/commands/project/switch.js +16 -36
  51. package/dist/commands/pull.js +7 -2
  52. package/dist/commands/push.js +9 -2
  53. package/dist/commands/role/diff.d.ts +2 -0
  54. package/dist/commands/role/diff.js +5 -0
  55. package/dist/commands/role/list.d.ts +2 -0
  56. package/dist/commands/role/list.js +4 -0
  57. package/dist/commands/role/pull.d.ts +2 -0
  58. package/dist/commands/role/pull.js +4 -0
  59. package/dist/commands/role/push.d.ts +2 -0
  60. package/dist/commands/role/push.js +4 -0
  61. package/dist/commands/role/rm.d.ts +2 -0
  62. package/dist/commands/role/rm.js +3 -0
  63. package/dist/commands/role/rollback.d.ts +2 -0
  64. package/dist/commands/role/rollback.js +5 -0
  65. package/dist/commands/status.d.ts +2 -3
  66. package/dist/commands/status.js +21 -53
  67. package/dist/commands/taxonomy/diff.d.ts +2 -0
  68. package/dist/commands/taxonomy/diff.js +5 -0
  69. package/dist/commands/taxonomy/list.d.ts +2 -0
  70. package/dist/commands/taxonomy/list.js +5 -0
  71. package/dist/commands/taxonomy/pull.d.ts +2 -0
  72. package/dist/commands/taxonomy/pull.js +4 -0
  73. package/dist/commands/taxonomy/push.d.ts +2 -0
  74. package/dist/commands/taxonomy/push.js +4 -0
  75. package/dist/commands/taxonomy/rm.d.ts +2 -0
  76. package/dist/commands/taxonomy/rm.js +3 -0
  77. package/dist/commands/taxonomy/rollback.d.ts +2 -0
  78. package/dist/commands/taxonomy/rollback.js +5 -0
  79. package/dist/commands/theme/template/push.d.ts +2 -0
  80. package/dist/commands/theme/template/push.js +26 -5
  81. package/dist/config/project-config.manager.d.ts +3 -3
  82. package/dist/config/project-config.manager.js +22 -31
  83. package/dist/hooks/finally.js +11 -3
  84. package/dist/lib/api-client.js +2 -1
  85. package/dist/lib/app-build.js +3 -1
  86. package/dist/lib/base.d.ts +1 -2
  87. package/dist/lib/base.js +30 -38
  88. package/dist/lib/declared-type-command.d.ts +35 -0
  89. package/dist/lib/declared-type-command.js +192 -0
  90. package/dist/lib/dev-watch.d.ts +1 -1
  91. package/dist/lib/dev-watch.js +7 -3
  92. package/dist/lib/github-release.js +4 -0
  93. package/dist/lib/local-callback-server.js +3 -1
  94. package/dist/lib/push-command.d.ts +1 -0
  95. package/dist/lib/push-command.js +31 -10
  96. package/dist/lib/put-or-create.js +3 -4
  97. package/dist/lib/resource-state.js +33 -80
  98. package/dist/lib/rollback-materialize.js +13 -24
  99. package/dist/lib/spinner.d.ts +2 -0
  100. package/dist/lib/spinner.js +72 -0
  101. package/dist/lib/temp-admin.d.ts +10 -1
  102. package/dist/lib/temp-admin.js +27 -2
  103. package/dist/lib/validate-local.js +31 -9
  104. package/dist/lib/wp-client.d.ts +1 -0
  105. package/dist/lib/wp-client.js +7 -1
  106. package/dist/lib/wp-site-diagnostic.js +3 -2
  107. package/dist/types/global-config.generated.d.ts +1 -5
  108. package/dist/types/project-config.generated.d.ts +28 -4
  109. package/dist/utils/composer.js +2 -1
  110. package/dist/utils/declared-type-format.d.ts +52 -0
  111. package/dist/utils/declared-type-format.js +233 -0
  112. package/dist/utils/resource-dirs.d.ts +9 -3
  113. package/dist/utils/resource-dirs.js +16 -4
  114. package/dist/utils/template-format.d.ts +1 -0
  115. package/dist/utils/theme-css.d.ts +19 -0
  116. package/dist/utils/theme-css.js +100 -0
  117. package/oclif.manifest.json +3012 -1133
  118. package/package.json +28 -15
  119. package/schemas/global-config.schema.json +2 -6
  120. package/schemas/project-config.schema.json +37 -7
  121. package/dist/commands/seo/diff.js +0 -5
  122. package/dist/commands/seo/list.d.ts +0 -18
  123. package/dist/commands/seo/list.js +0 -58
  124. package/dist/commands/seo/pull.d.ts +0 -24
  125. package/dist/commands/seo/pull.js +0 -89
  126. package/dist/commands/seo/push.d.ts +0 -29
  127. package/dist/commands/seo/push.js +0 -178
  128. package/dist/commands/seo/rollback.js +0 -5
  129. package/dist/utils/seo-format.d.ts +0 -29
  130. package/dist/utils/seo-format.js +0 -18
  131. /package/dist/commands/{seo → cpt}/diff.d.ts +0 -0
  132. /package/dist/commands/{seo → cpt}/rollback.d.ts +0 -0
@@ -0,0 +1,5 @@
1
+ import { resourceDiffCommand } from '../../lib/diff-command.js';
2
+ export default resourceDiffCommand('cpt', {
3
+ description: 'Show what differs in custom post types between your local cpt/ files and a WordPress environment, or between two environments',
4
+ pathNoun: 'cpt directory',
5
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredListCommand>;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { declaredListCommand } from '../../lib/declared-type-command.js';
2
+ import { CPT_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredListCommand(CPT_SPEC, 'List the post types registered on WordPress and where each comes from (Loopress, WordPress, ACF, CPT UI, a theme or plugin). ' +
4
+ 'Only Loopress ones are pulled and pushed.', (count) => `${count} published`);
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredPullCommand>;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { declaredPullCommand } from '../../lib/declared-type-command.js';
2
+ import { CPT_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredPullCommand(CPT_SPEC, 'Pull the custom post types Loopress manages on WordPress into cpt/<slug>.json files. ' +
4
+ 'Post types registered by a theme or another plugin are never pulled, see `lps cpt list`.');
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredPushCommand>;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { declaredPushCommand } from '../../lib/declared-type-command.js';
2
+ import { CPT_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredPushCommand(CPT_SPEC, 'Push local custom post types (cpt/<slug>.json, register_post_type() arguments) to WordPress. ' +
4
+ 'Create or update only, never deletes a post type: use `lps cpt rm` for that.');
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredRmCommand>;
2
+ export default _default;
@@ -0,0 +1,3 @@
1
+ import { declaredRmCommand } from '../../lib/declared-type-command.js';
2
+ import { CPT_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredRmCommand(CPT_SPEC, 'Stop registering a Loopress custom post type on WordPress. Its posts are kept in the database, hidden until the post type is pushed again.', "Its posts are kept in the database, hidden until it's pushed again.");
@@ -0,0 +1,5 @@
1
+ import { resourceRollbackCommand } from '../../lib/rollback-command.js';
2
+ export default resourceRollbackCommand('cpt', {
3
+ description: 'Restore custom post types to a snapshot taken automatically before an earlier `lps cpt push`',
4
+ pathNoun: 'cpt directory',
5
+ });
@@ -48,18 +48,28 @@ export default class Dev extends Command {
48
48
  }
49
49
  // Sequential on purpose (see notes technique: parallelizing push is out of scope). A failed
50
50
  // push is logged and swallowed here, never thrown, so one bad batch can't take the watcher down.
51
+ // Types sharing a command run it once: templates, parts, css and blocks all rewrite the whole
52
+ // child theme through `theme:template:push`.
51
53
  async pushBatch(changesByType, targets) {
54
+ const byCommand = new Map();
52
55
  for (const [type, paths] of changesByType) {
53
56
  const target = targets.find((t) => t.type === type);
54
57
  if (!target)
55
58
  continue;
56
- this.log(`\n→ ${type} changed (${paths.map((path) => basename(path)).join(', ')}), pushing to local...`);
59
+ const batch = byCommand.get(target.commandId) ?? { paths: [], types: [] };
60
+ batch.types.push(type);
61
+ batch.paths.push(...paths);
62
+ byCommand.set(target.commandId, batch);
63
+ }
64
+ for (const [commandId, { paths, types }] of byCommand) {
65
+ const label = types.join(', ');
66
+ this.log(`\n→ ${label} changed (${paths.map((path) => basename(path)).join(', ')}), pushing to local...`);
57
67
  try {
58
- await this.config.runCommand(target.commandId, ['--env', 'local']);
59
- this.log(`✓ ${type} synced`);
68
+ await this.config.runCommand(commandId, ['--env', 'local']);
69
+ this.log(`✓ ${label} synced`);
60
70
  }
61
71
  catch (error) {
62
- this.log(`✗ ${type} failed: ${error.message}`);
72
+ this.log(`✗ ${label} failed: ${error.message}`);
63
73
  }
64
74
  }
65
75
  }
@@ -7,7 +7,7 @@ const RESOURCES = [...RESOURCE_STATE_PROVIDERS.map((provider) => provider.resour
7
7
  export default class Diff extends DiffCommand {
8
8
  static description = [
9
9
  'Show what differs between your local tracked files and a WordPress environment, or between two environments.',
10
- 'Covers snippets, forms, ACF, API routes, Hooks, templates, template parts, pages, SEO, Options, Menus, Theme styles, and Composer. Plugins and themes have their own `lps plugin status` / `lps theme status`.',
10
+ 'Covers snippets, forms, ACF, API routes, Hooks, templates, template parts, pages, Options, Menus, Theme styles, and Composer. Plugins and themes have their own `lps plugin status` / `lps theme status`.',
11
11
  'Exit code: 0 in sync, 1 on drift, 2 when a resource could not be compared, so it doubles as a CI drift gate.',
12
12
  ].join(' ');
13
13
  static enableJsonFlag = true;
@@ -19,6 +19,7 @@ export default class Doctor extends LoopressCommand {
19
19
  private readonly checks;
20
20
  run(): Promise<DoctorResult>;
21
21
  private check;
22
+ private checkLeftoverTempAdmins;
22
23
  private out;
23
24
  private reportPluginVersion;
24
25
  }
@@ -1,6 +1,7 @@
1
1
  import { configManager } from '../config/project-config.manager.js';
2
2
  import { LoopressCommand } from '../lib/base.js';
3
- import { isNotFoundError } from '../lib/wp-client.js';
3
+ import { findTempAdmins } from '../lib/temp-admin.js';
4
+ import { isForbiddenError, isNotFoundError } from '../lib/wp-client.js';
4
5
  import { diagnoseWpSite } from '../lib/wp-site-diagnostic.js';
5
6
  import { pluralize } from '../utils/pluralize.js';
6
7
  export default class Doctor extends LoopressCommand {
@@ -32,7 +33,10 @@ export default class Doctor extends LoopressCommand {
32
33
  await this.check('Loopress plugin installed (loopress/v1 endpoints)', async () => this.wp.get('loopress/v1'));
33
34
  // wp/v2/users/me is WordPress core and requires authentication, so it validates the
34
35
  // application password without depending on any Loopress feature.
35
- await this.check('Credentials accepted (authenticated request)', async () => this.wp.get('wp/v2/users/me'));
36
+ const isAuthenticated = await this.check('Credentials accepted (authenticated request)', async () => this.wp.get('wp/v2/users/me'));
37
+ // Skipped when the credentials were just rejected: it would only repeat that same 401.
38
+ if (isAuthenticated)
39
+ await this.checkLeftoverTempAdmins();
36
40
  pluginVersion = await this.reportPluginVersion();
37
41
  }
38
42
  else {
@@ -72,6 +76,30 @@ export default class Doctor extends LoopressCommand {
72
76
  return false;
73
77
  }
74
78
  }
79
+ // Report only, never deletes: a doctor run shouldn't change the site. A leftover is a live
80
+ // administrator with a password nobody holds, so it fails the run. Credentials without
81
+ // `list_users` can't look, which is informational, not a failure: nothing was found wrong.
82
+ async checkLeftoverTempAdmins() {
83
+ let lookup;
84
+ try {
85
+ lookup = { leftovers: await findTempAdmins(this.wp) };
86
+ }
87
+ catch (error) {
88
+ lookup = { error };
89
+ }
90
+ if ('error' in lookup && isForbiddenError(lookup.error)) {
91
+ this.out('- Leftover temporary admin accounts: not checked, these credentials cannot list users.');
92
+ return;
93
+ }
94
+ await this.check('No leftover temporary admin accounts', async () => {
95
+ if ('error' in lookup)
96
+ throw lookup.error;
97
+ if (lookup.leftovers.length > 0) {
98
+ const names = lookup.leftovers.map((admin) => admin.username).join(', ');
99
+ throw new Error(`Found ${names}, left by an interrupted Loopress Full install. Re-run \`lps project config\` to remove them, or delete them in wp-admin > Users.`);
100
+ }
101
+ });
102
+ }
75
103
  out(message) {
76
104
  if (!this.jsonEnabled())
77
105
  this.log(message);
@@ -0,0 +1,2 @@
1
+ declare const _default: typeof import("../../lib/diff-command.js").DiffCommand;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { resourceDiffCommand } from '../../lib/diff-command.js';
2
+ export default resourceDiffCommand('meta', {
3
+ description: 'Show what differs in post meta keys between your local meta/ files and a WordPress environment, or between two environments',
4
+ pathNoun: 'meta directory',
5
+ });
@@ -0,0 +1,19 @@
1
+ import { LoopressCommand } from '../../lib/base.js';
2
+ type RegisteredMetaKey = {
3
+ acf: boolean;
4
+ conflict: boolean;
5
+ key: string;
6
+ managed: boolean;
7
+ postType: string;
8
+ showInRest: boolean;
9
+ single: boolean;
10
+ source: 'loopress' | 'other';
11
+ type: string;
12
+ };
13
+ export default class MetaList extends LoopressCommand {
14
+ static description: string;
15
+ static enableJsonFlag: boolean;
16
+ static examples: string[];
17
+ run(): Promise<RegisteredMetaKey[]>;
18
+ }
19
+ export {};
@@ -0,0 +1,21 @@
1
+ import { LoopressCommand } from '../../lib/base.js';
2
+ import { META_SPEC } from '../../utils/declared-type-format.js';
3
+ export default class MetaList extends LoopressCommand {
4
+ static description = 'List the meta keys registered for each post type on WordPress (with register_post_meta()), and whether Loopress declares them. ' +
5
+ 'Only Loopress ones are pulled and pushed.';
6
+ static enableJsonFlag = true;
7
+ static examples = ['$ lps meta list'];
8
+ async run() {
9
+ const keys = await this.wp.get(META_SPEC.registeredEndpoint);
10
+ for (const meta of keys) {
11
+ const source = meta.source === 'loopress' ? 'Loopress' : 'theme or plugin';
12
+ const rest = meta.showInRest ? ', in REST' : '';
13
+ const acf = meta.acf ? ', same name as an ACF field' : '';
14
+ this.log(`${meta.postType}.${meta.key} (${meta.type}): ${source}${rest}${acf}`);
15
+ if (meta.conflict) {
16
+ this.warn(`${meta.postType}.${meta.key}: declared in meta/ but already registered by a theme or plugin, the Loopress version is skipped`);
17
+ }
18
+ }
19
+ return keys;
20
+ }
21
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredPullCommand>;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { declaredPullCommand } from '../../lib/declared-type-command.js';
2
+ import { META_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredPullCommand(META_SPEC, 'Pull the post meta keys Loopress manages on WordPress into meta/<post-type>.json files, never their values. ' +
4
+ 'Keys registered by a theme or another plugin are never pulled, see `lps meta list`.');
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredPushCommand>;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { declaredPushCommand } from '../../lib/declared-type-command.js';
2
+ import { META_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredPushCommand(META_SPEC, 'Push local post meta keys (meta/<post-type>.json, each key mapped to its register_post_meta() arguments) to WordPress. ' +
4
+ 'Create or update only, never deletes (use `lps meta rm`), never touches meta values.');
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredRmCommand>;
2
+ export default _default;
@@ -0,0 +1,3 @@
1
+ import { declaredRmCommand } from '../../lib/declared-type-command.js';
2
+ import { META_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredRmCommand(META_SPEC, 'Stop registering the Loopress meta keys of one post type (its meta/<post-type>.json file) on WordPress. Their values are kept in the database.', "Their values are kept in the database, hidden until it's pushed again.");
@@ -0,0 +1,2 @@
1
+ declare const _default: typeof import("../../lib/base.js").LoopressCommand;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { resourceRollbackCommand } from '../../lib/rollback-command.js';
2
+ export default resourceRollbackCommand('meta', {
3
+ description: 'Restore post meta keys to a snapshot taken automatically before an earlier `lps meta push`',
4
+ pathNoun: 'meta directory',
5
+ });
@@ -1,6 +1,7 @@
1
1
  import { Flags, ux } from '@oclif/core';
2
2
  import ttyTable from 'tty-table';
3
3
  import { LoopressCommand } from '../../lib/base.js';
4
+ import { withSpinner } from '../../lib/spinner.js';
4
5
  import { OPTIONS_ENDPOINT } from '../../utils/option-format.js';
5
6
  import { pluralize } from '../../utils/pluralize.js';
6
7
  // Same green/yellow pairing drives both SOURCE? and PLUGIN, so a row's whole "where did this come
@@ -127,14 +128,7 @@ export default class List extends LoopressCommand {
127
128
  // Every call scans active plugins' own PHP source for names the naming guess missed (not
128
129
  // opt-in, roughly 1-3s on a real site once vendor/tests/languages are pruned from the scan;
129
130
  // see OptionsService::listOptionNames() server-side), hence the spinner unconditionally.
130
- ux.action.start('Fetching and verifying options');
131
- let options;
132
- try {
133
- options = await this.wp.get(OPTIONS_ENDPOINT);
134
- }
135
- finally {
136
- ux.action.stop();
137
- }
131
+ let options = await withSpinner('Fetching and verifying options', async () => this.wp.get(OPTIONS_ENDPOINT));
138
132
  if (flags['no-core'])
139
133
  options = options.filter((option) => !option.core);
140
134
  if (options.length === 0) {
@@ -0,0 +1,2 @@
1
+ declare const _default: typeof import("../../lib/diff-command.js").DiffCommand;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { resourceDiffCommand } from '../../lib/diff-command.js';
2
+ export default resourceDiffCommand('pattern', {
3
+ description: 'Show what differs in block patterns between your local patterns/ files and a WordPress environment, or between two environments',
4
+ pathNoun: 'patterns directory',
5
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredListCommand>;
2
+ export default _default;
@@ -0,0 +1,3 @@
1
+ import { declaredListCommand } from '../../lib/declared-type-command.js';
2
+ import { PATTERN_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredListCommand(PATTERN_SPEC, 'List the block patterns registered on WordPress by Loopress, the theme and plugins (not WordPress core). Only Loopress ones are pulled and pushed.', () => '');
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredPullCommand>;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { declaredPullCommand } from '../../lib/declared-type-command.js';
2
+ import { PATTERN_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredPullCommand(PATTERN_SPEC, 'Pull the block patterns Loopress manages on WordPress into patterns/<slug>.html files. ' +
4
+ 'Patterns registered by the theme or a plugin are never pulled, see `lps pattern list`.');
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredPushCommand>;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { declaredPushCommand } from '../../lib/declared-type-command.js';
2
+ import { PATTERN_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredPushCommand(PATTERN_SPEC, 'Push local block patterns (patterns/<slug>.html, block markup under a title/categories header) to WordPress, ' +
4
+ 'registered as loopress/<slug>. Create or update only, never deletes a pattern (use `lps pattern rm`).');
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../lib/php-files-command.js").CommandClass<import("../../lib/declared-type-command.js").DeclaredRmCommand>;
2
+ export default _default;
@@ -0,0 +1,3 @@
1
+ import { declaredRmCommand } from '../../lib/declared-type-command.js';
2
+ import { PATTERN_SPEC } from '../../utils/declared-type-format.js';
3
+ export default declaredRmCommand(PATTERN_SPEC, 'Stop registering a Loopress block pattern on WordPress. A wp:pattern reference to it renders nothing, copies already inserted in posts stay.', 'A wp:pattern reference to it renders nothing until it is pushed again, copies already inserted in posts stay.');
@@ -0,0 +1,2 @@
1
+ declare const _default: typeof import("../../lib/base.js").LoopressCommand;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { resourceRollbackCommand } from '../../lib/rollback-command.js';
2
+ export default resourceRollbackCommand('pattern', {
3
+ description: 'Restore block patterns to a snapshot taken automatically before an earlier `lps pattern push`',
4
+ pathNoun: 'patterns directory',
5
+ });
@@ -1,5 +1,6 @@
1
1
  import got from 'got';
2
2
  import { LoopressCommand } from '../../lib/base.js';
3
+ import { withSpinner } from '../../lib/spinner.js';
3
4
  import { pinVersion } from '../../types/plugin.js';
4
5
  import { compareVersions } from '../../utils/version.js';
5
6
  const WPVULN_BASE = 'https://www.wpvulnerability.net/plugin';
@@ -19,7 +20,7 @@ export default class Audit extends LoopressCommand {
19
20
  const advisories = [];
20
21
  const health = [];
21
22
  for (const slug of slugs) {
22
- const found = await this.auditOne(slug, pinVersion(manifest[slug]));
23
+ const found = await withSpinner(`Auditing ${slug}`, async () => this.auditOne(slug, pinVersion(manifest[slug])));
23
24
  advisories.push(...found.advisories);
24
25
  health.push(...found.health);
25
26
  }
@@ -7,4 +7,5 @@ export default class Config extends Command {
7
7
  private promptManualCredentials;
8
8
  private resolveCredentials;
9
9
  private resolveProject;
10
+ private sweepLeftoverTempAdmins;
10
11
  }
@@ -4,9 +4,12 @@ import { configManager } from '../../config/project-config.manager.js';
4
4
  import { bootstrapLoopressFull } from '../../lib/bootstrap-full-install.js';
5
5
  import { isInteractive } from '../../lib/interactive.js';
6
6
  import { isLoopressFullActive } from '../../lib/plugin-detection.js';
7
+ import { withSpinner } from '../../lib/spinner.js';
8
+ import { sweepTempAdmins } from '../../lib/temp-admin.js';
7
9
  import { authorizeWithBrowser } from '../../lib/wp-authorize-flow.js';
8
10
  import { isLocalEnvironment, WpClient } from '../../lib/wp-client.js';
9
11
  import { diagnoseWpSite } from '../../lib/wp-site-diagnostic.js';
12
+ import { pluralize } from '../../utils/pluralize.js';
10
13
  const NEW_PROJECT = '__new__';
11
14
  const AUTH_BROWSER = 'browser';
12
15
  const AUTH_MANUAL = 'manual';
@@ -96,6 +99,10 @@ export default class Config extends Command {
96
99
  if (!token)
97
100
  return; // env was just built with one a few lines up; guard only narrows the type
98
101
  const wp = new WpClient(url, token, name);
102
+ // Before the "already active" early return: an install killed mid-way (SIGKILL, power loss)
103
+ // may have activated Full and still left its temp admin behind, and re-running this command
104
+ // is the documented fix for both.
105
+ await this.sweepLeftoverTempAdmins(wp);
99
106
  // A detection failure (network hiccup, plugins REST disabled) is treated as "assume it's
100
107
  // there" rather than surprising the user with an account-creating prompt they didn't ask
101
108
  // for; `lps project config` can simply be re-run if it genuinely isn't installed.
@@ -109,7 +116,9 @@ export default class Config extends Command {
109
116
  if (!isProceed)
110
117
  return;
111
118
  try {
112
- await bootstrapLoopressFull(wp, url, (message) => { this.log(message); });
119
+ await withSpinner('Installing Loopress Full', async () => bootstrapLoopressFull(wp, url, (message) => {
120
+ this.log(message);
121
+ }));
113
122
  }
114
123
  catch (error) {
115
124
  this.warn(error.message);
@@ -181,4 +190,14 @@ export default class Config extends Command {
181
190
  });
182
191
  return { projectId: configManager.createProjectId(projectName.trim()), projectName: projectName.trim() };
183
192
  }
193
+ async sweepLeftoverTempAdmins(wp) {
194
+ try {
195
+ const removed = await sweepTempAdmins(wp);
196
+ if (removed.length > 0)
197
+ this.log(`✓ Removed ${pluralize(removed.length, 'leftover temporary admin account')}: ${removed.join(', ')}`);
198
+ }
199
+ catch (error) {
200
+ this.warn(error.message);
201
+ }
202
+ }
184
203
  }
@@ -1,6 +1,6 @@
1
1
  import { Command } from '@oclif/core';
2
2
  type EnvironmentInfo = {
3
- isCurrent: boolean;
3
+ isDefault: boolean;
4
4
  name: string;
5
5
  url: string;
6
6
  };
@@ -14,15 +14,15 @@ export default class List extends Command {
14
14
  }
15
15
  return projects.map((project) => this.logProject(project));
16
16
  }
17
- // Only name/url/isCurrent are surfaced, deliberately: EnvironmentConfig also carries the
17
+ // Only name/url/isDefault are surfaced, deliberately: EnvironmentConfig also carries the
18
18
  // WordPress application password token, which must never end up in --json output.
19
19
  logEnvironment(env) {
20
- const envMarker = c(env.isCurrent ? 'cyan' : 'dim', '·');
21
- const envName = c(env.isCurrent ? 'cyan' : 'dim', env.name.padEnd(15));
20
+ const envMarker = c(env.isDefault ? 'cyan' : 'dim', '·');
21
+ const envName = c(env.isDefault ? 'cyan' : 'dim', env.name.padEnd(15));
22
22
  const envUrl = c('dim', env.url);
23
- const activeTag = env.isCurrent ? ` ${c('cyan', '←')}` : '';
24
- this.log(` ${envMarker} ${envName} ${envUrl}${activeTag}`);
25
- return { isCurrent: env.isCurrent, name: env.name, url: env.url };
23
+ const defaultTag = env.isDefault ? ` ${c('cyan', '[default]')}` : '';
24
+ this.log(` ${envMarker} ${envName} ${envUrl}${defaultTag}`);
25
+ return { isDefault: env.isDefault, name: env.name, url: env.url };
26
26
  }
27
27
  logProject(project) {
28
28
  const marker = project.isCurrent ? c('green', '●') : c('dim', '○');
@@ -3,6 +3,7 @@ import { Listr } from 'listr2';
3
3
  import { authManager } from '../../config/auth.manager.js';
4
4
  import { configManager } from '../../config/project-config.manager.js';
5
5
  import { ApiClient } from '../../lib/api-client.js';
6
+ import { withoutSpinner } from '../../lib/spinner.js';
6
7
  import { pluralize } from '../../utils/pluralize.js';
7
8
  export default class Pull extends Command {
8
9
  static description = 'Pull projects and environments from your Loopress account that are not configured locally yet';
@@ -23,14 +24,16 @@ export default class Pull extends Command {
23
24
  let projectCount = 0;
24
25
  let environmentCount = 0;
25
26
  if (newApiProjects.length > 0) {
26
- await new Listr(newApiProjects.map((apiProject) => ({
27
- task: (_ctx, task) => {
28
- this.pullProject(apiProject, task);
29
- projectCount++;
30
- environmentCount += apiProject.environments.length;
31
- },
32
- title: `Pull project "${apiProject.name}" from the API`,
33
- })), { concurrent: false, exitOnError: false }).run();
27
+ await withoutSpinner(async () => {
28
+ await new Listr(newApiProjects.map((apiProject) => ({
29
+ task: (_ctx, task) => {
30
+ this.pullProject(apiProject, task);
31
+ projectCount++;
32
+ environmentCount += apiProject.environments.length;
33
+ },
34
+ title: `Pull project "${apiProject.name}" from the API`,
35
+ })), { concurrent: false, exitOnError: false }).run();
36
+ });
34
37
  }
35
38
  this.log(`\n✓ Pulled ${pluralize(projectCount, 'project')}, ${pluralize(environmentCount, 'environment')} from your Loopress account`);
36
39
  }
@@ -6,6 +6,7 @@ import { configManager } from '../../config/project-config.manager.js';
6
6
  import { ApiClient } from '../../lib/api-client.js';
7
7
  import { LoopressCommand } from '../../lib/base.js';
8
8
  import { isInteractive } from '../../lib/interactive.js';
9
+ import { withoutSpinner } from '../../lib/spinner.js';
9
10
  import { pluralize } from '../../utils/pluralize.js';
10
11
  import { toSlug } from '../../utils/to-slug.js';
11
12
  export default class Push extends Command {
@@ -66,15 +67,17 @@ export default class Push extends Command {
66
67
  if (envsNeedingWork.length === 0)
67
68
  return 0;
68
69
  let count = 0;
69
- await new Listr(envsNeedingWork.map(({ apiProjectId, envPlan, projectName }) => ({
70
- task: async (_ctx, task) => {
71
- await this.applyEnvironment(api, apiProjectId, envPlan, task);
72
- count++;
73
- },
74
- title: envPlan.action === 'create'
75
- ? `Create environment "${envPlan.env.name}" on "${projectName}"`
76
- : `Link environment "${envPlan.env.name}" on "${projectName}"`,
77
- })), { concurrent: false, exitOnError: false }).run();
70
+ await withoutSpinner(async () => {
71
+ await new Listr(envsNeedingWork.map(({ apiProjectId, envPlan, projectName }) => ({
72
+ task: async (_ctx, task) => {
73
+ await this.applyEnvironment(api, apiProjectId, envPlan, task);
74
+ count++;
75
+ },
76
+ title: envPlan.action === 'create'
77
+ ? `Create environment "${envPlan.env.name}" on "${projectName}"`
78
+ : `Link environment "${envPlan.env.name}" on "${projectName}"`,
79
+ })), { concurrent: false, exitOnError: false }).run();
80
+ });
78
81
  return count;
79
82
  }
80
83
  async applyProject(api, plan, task) {
@@ -101,15 +104,17 @@ export default class Push extends Command {
101
104
  if (projectsNeedingWork.length === 0)
102
105
  return 0;
103
106
  let count = 0;
104
- await new Listr(projectsNeedingWork.map((plan) => ({
105
- task: async (_ctx, task) => {
106
- await this.applyProject(api, plan, task);
107
- count++;
108
- },
109
- title: plan.action === 'create'
110
- ? `Create project "${plan.project.name}" on the API`
111
- : `Link project "${plan.project.name}" to the API`,
112
- })), { concurrent: false, exitOnError: false }).run();
107
+ await withoutSpinner(async () => {
108
+ await new Listr(projectsNeedingWork.map((plan) => ({
109
+ task: async (_ctx, task) => {
110
+ await this.applyProject(api, plan, task);
111
+ count++;
112
+ },
113
+ title: plan.action === 'create'
114
+ ? `Create project "${plan.project.name}" on the API`
115
+ : `Link project "${plan.project.name}" to the API`,
116
+ })), { concurrent: false, exitOnError: false }).run();
117
+ });
113
118
  return count;
114
119
  }
115
120
  collectEnvsNeedingWork(projectPlans, envPlansByProject) {
@@ -24,7 +24,7 @@ export default class Remove extends Command {
24
24
  const envChoices = configManager.listEnvironments(project.id).map((env) => {
25
25
  targets.push({ env: env.name, kind: 'env', projectId: project.id, projectName: project.name });
26
26
  return {
27
- name: ` ${env.name.padEnd(20)} ${env.url}${env.isCurrent ? ' [current]' : ''}`,
27
+ name: ` ${env.name.padEnd(20)} ${env.url}`,
28
28
  value: String(targets.length - 1),
29
29
  };
30
30
  });
@@ -3,5 +3,4 @@ export default class Switch extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  run(): Promise<void>;
6
- private resolveSelection;
7
6
  }