@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.
- package/README.md +1301 -345
- package/dist/commands/cpt/diff.js +5 -0
- package/dist/commands/cpt/list.d.ts +2 -0
- package/dist/commands/cpt/list.js +4 -0
- package/dist/commands/cpt/pull.d.ts +2 -0
- package/dist/commands/cpt/pull.js +4 -0
- package/dist/commands/cpt/push.d.ts +2 -0
- package/dist/commands/cpt/push.js +4 -0
- package/dist/commands/cpt/rm.d.ts +2 -0
- package/dist/commands/cpt/rm.js +3 -0
- package/dist/commands/cpt/rollback.js +5 -0
- package/dist/commands/dev.js +14 -4
- package/dist/commands/diff.js +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +30 -2
- package/dist/commands/meta/diff.d.ts +2 -0
- package/dist/commands/meta/diff.js +5 -0
- package/dist/commands/meta/list.d.ts +19 -0
- package/dist/commands/meta/list.js +21 -0
- package/dist/commands/meta/pull.d.ts +2 -0
- package/dist/commands/meta/pull.js +4 -0
- package/dist/commands/meta/push.d.ts +2 -0
- package/dist/commands/meta/push.js +4 -0
- package/dist/commands/meta/rm.d.ts +2 -0
- package/dist/commands/meta/rm.js +3 -0
- package/dist/commands/meta/rollback.d.ts +2 -0
- package/dist/commands/meta/rollback.js +5 -0
- package/dist/commands/option/list.js +2 -8
- package/dist/commands/pattern/diff.d.ts +2 -0
- package/dist/commands/pattern/diff.js +5 -0
- package/dist/commands/pattern/list.d.ts +2 -0
- package/dist/commands/pattern/list.js +3 -0
- package/dist/commands/pattern/pull.d.ts +2 -0
- package/dist/commands/pattern/pull.js +4 -0
- package/dist/commands/pattern/push.d.ts +2 -0
- package/dist/commands/pattern/push.js +4 -0
- package/dist/commands/pattern/rm.d.ts +2 -0
- package/dist/commands/pattern/rm.js +3 -0
- package/dist/commands/pattern/rollback.d.ts +2 -0
- package/dist/commands/pattern/rollback.js +5 -0
- package/dist/commands/plugin/audit.js +2 -1
- package/dist/commands/project/config.d.ts +1 -0
- package/dist/commands/project/config.js +20 -1
- package/dist/commands/project/list.d.ts +1 -1
- package/dist/commands/project/list.js +6 -6
- package/dist/commands/project/pull.js +11 -8
- package/dist/commands/project/push.js +23 -18
- package/dist/commands/project/remove.js +1 -1
- package/dist/commands/project/switch.d.ts +0 -1
- package/dist/commands/project/switch.js +16 -36
- package/dist/commands/pull.js +7 -2
- package/dist/commands/push.js +9 -2
- package/dist/commands/role/diff.d.ts +2 -0
- package/dist/commands/role/diff.js +5 -0
- package/dist/commands/role/list.d.ts +2 -0
- package/dist/commands/role/list.js +4 -0
- package/dist/commands/role/pull.d.ts +2 -0
- package/dist/commands/role/pull.js +4 -0
- package/dist/commands/role/push.d.ts +2 -0
- package/dist/commands/role/push.js +4 -0
- package/dist/commands/role/rm.d.ts +2 -0
- package/dist/commands/role/rm.js +3 -0
- package/dist/commands/role/rollback.d.ts +2 -0
- package/dist/commands/role/rollback.js +5 -0
- package/dist/commands/status.d.ts +2 -3
- package/dist/commands/status.js +21 -53
- package/dist/commands/taxonomy/diff.d.ts +2 -0
- package/dist/commands/taxonomy/diff.js +5 -0
- package/dist/commands/taxonomy/list.d.ts +2 -0
- package/dist/commands/taxonomy/list.js +5 -0
- package/dist/commands/taxonomy/pull.d.ts +2 -0
- package/dist/commands/taxonomy/pull.js +4 -0
- package/dist/commands/taxonomy/push.d.ts +2 -0
- package/dist/commands/taxonomy/push.js +4 -0
- package/dist/commands/taxonomy/rm.d.ts +2 -0
- package/dist/commands/taxonomy/rm.js +3 -0
- package/dist/commands/taxonomy/rollback.d.ts +2 -0
- package/dist/commands/taxonomy/rollback.js +5 -0
- package/dist/commands/theme/template/push.d.ts +2 -0
- package/dist/commands/theme/template/push.js +26 -5
- package/dist/config/project-config.manager.d.ts +3 -3
- package/dist/config/project-config.manager.js +22 -31
- package/dist/hooks/finally.js +11 -3
- package/dist/lib/api-client.js +2 -1
- package/dist/lib/app-build.js +3 -1
- package/dist/lib/base.d.ts +1 -2
- package/dist/lib/base.js +30 -38
- package/dist/lib/declared-type-command.d.ts +35 -0
- package/dist/lib/declared-type-command.js +192 -0
- package/dist/lib/dev-watch.d.ts +1 -1
- package/dist/lib/dev-watch.js +7 -3
- package/dist/lib/github-release.js +4 -0
- package/dist/lib/local-callback-server.js +3 -1
- package/dist/lib/push-command.d.ts +1 -0
- package/dist/lib/push-command.js +31 -10
- package/dist/lib/put-or-create.js +3 -4
- package/dist/lib/resource-state.js +33 -80
- package/dist/lib/rollback-materialize.js +13 -24
- package/dist/lib/spinner.d.ts +2 -0
- package/dist/lib/spinner.js +72 -0
- package/dist/lib/temp-admin.d.ts +10 -1
- package/dist/lib/temp-admin.js +27 -2
- package/dist/lib/validate-local.js +31 -9
- package/dist/lib/wp-client.d.ts +1 -0
- package/dist/lib/wp-client.js +7 -1
- package/dist/lib/wp-site-diagnostic.js +3 -2
- package/dist/types/global-config.generated.d.ts +1 -5
- package/dist/types/project-config.generated.d.ts +28 -4
- package/dist/utils/composer.js +2 -1
- package/dist/utils/declared-type-format.d.ts +52 -0
- package/dist/utils/declared-type-format.js +233 -0
- package/dist/utils/resource-dirs.d.ts +9 -3
- package/dist/utils/resource-dirs.js +16 -4
- package/dist/utils/template-format.d.ts +1 -0
- package/dist/utils/theme-css.d.ts +19 -0
- package/dist/utils/theme-css.js +100 -0
- package/oclif.manifest.json +3012 -1133
- package/package.json +28 -15
- package/schemas/global-config.schema.json +2 -6
- package/schemas/project-config.schema.json +37 -7
- package/dist/commands/seo/diff.js +0 -5
- package/dist/commands/seo/list.d.ts +0 -18
- package/dist/commands/seo/list.js +0 -58
- package/dist/commands/seo/pull.d.ts +0 -24
- package/dist/commands/seo/pull.js +0 -89
- package/dist/commands/seo/push.d.ts +0 -29
- package/dist/commands/seo/push.js +0 -178
- package/dist/commands/seo/rollback.js +0 -5
- package/dist/utils/seo-format.d.ts +0 -29
- package/dist/utils/seo-format.js +0 -18
- /package/dist/commands/{seo → cpt}/diff.d.ts +0 -0
- /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,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,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,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,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.");
|
package/dist/commands/dev.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
59
|
-
this.log(`✓ ${
|
|
68
|
+
await this.config.runCommand(commandId, ['--env', 'local']);
|
|
69
|
+
this.log(`✓ ${label} synced`);
|
|
60
70
|
}
|
|
61
71
|
catch (error) {
|
|
62
|
-
this.log(`✗ ${
|
|
72
|
+
this.log(`✗ ${label} failed: ${error.message}`);
|
|
63
73
|
}
|
|
64
74
|
}
|
|
65
75
|
}
|
package/dist/commands/diff.js
CHANGED
|
@@ -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,
|
|
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;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { configManager } from '../config/project-config.manager.js';
|
|
2
2
|
import { LoopressCommand } from '../lib/base.js';
|
|
3
|
-
import {
|
|
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,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,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,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,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.");
|
|
@@ -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
|
-
|
|
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,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,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,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,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,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,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
|
}
|
|
@@ -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) => {
|
|
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
|
}
|
|
@@ -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/
|
|
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.
|
|
21
|
-
const envName = c(env.
|
|
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
|
|
24
|
-
this.log(` ${envMarker} ${envName} ${envUrl}${
|
|
25
|
-
return {
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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}
|
|
27
|
+
name: ` ${env.name.padEnd(20)} ${env.url}`,
|
|
28
28
|
value: String(targets.length - 1),
|
|
29
29
|
};
|
|
30
30
|
});
|