@loopress/cli 0.29.0 → 0.31.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 +1369 -360
- package/dist/commands/acf/push.js +2 -1
- package/dist/commands/composer/push.d.ts +1 -0
- package/dist/commands/composer/push.js +13 -0
- package/dist/commands/content/copy.d.ts +28 -0
- package/dist/commands/content/copy.js +97 -0
- 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 +2 -0
- package/dist/commands/doctor.js +57 -2
- package/dist/commands/form/push.js +4 -1
- 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/option/push.js +4 -2
- package/dist/commands/page/push.js +3 -2
- 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 +43 -25
- 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/promote.d.ts +1 -0
- package/dist/commands/promote.js +7 -3
- package/dist/commands/pull.d.ts +2 -1
- package/dist/commands/pull.js +31 -6
- package/dist/commands/push.d.ts +4 -2
- package/dist/commands/push.js +51 -19
- 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/snippet/push.d.ts +1 -0
- package/dist/commands/snippet/push.js +24 -3
- 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/commands/theme/version/pull.js +6 -1
- 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 +2 -2
- package/dist/lib/base.js +33 -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/diff-command.d.ts +2 -0
- package/dist/lib/diff-command.js +24 -3
- package/dist/lib/github-release.js +4 -0
- package/dist/lib/load-snippets.js +4 -0
- package/dist/lib/local-callback-server.js +3 -1
- package/dist/lib/php-files-command.d.ts +8 -0
- package/dist/lib/php-files-command.js +5 -0
- package/dist/lib/push-command.d.ts +5 -2
- package/dist/lib/push-command.js +96 -15
- package/dist/lib/put-or-create.js +3 -4
- package/dist/lib/resource-state.js +38 -85
- package/dist/lib/rollback-command.js +9 -2
- 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 +40 -12
- package/dist/lib/wp-client.d.ts +1 -0
- package/dist/lib/wp-client.js +21 -2
- 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/types/snippet.d.ts +1 -0
- 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 +3294 -1213
- package/package.json +32 -16
- 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
package/dist/commands/push.js
CHANGED
|
@@ -1,47 +1,56 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { join } from 'node:path';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
4
|
import { LoopressCommand } from '../lib/base.js';
|
|
5
5
|
import { guardProductionPush } from '../lib/guard-production-push.js';
|
|
6
6
|
import { stdoutToStderr } from '../lib/json-delegation.js';
|
|
7
|
+
import { PushCommand } from '../lib/push-command.js';
|
|
7
8
|
import { isLocalEnvironment } from '../lib/wp-client.js';
|
|
8
9
|
import { wpackagistRequire } from '../utils/composer.js';
|
|
9
|
-
import {
|
|
10
|
+
import { resolveResourceDir } from '../utils/resource-dirs.js';
|
|
10
11
|
// Dependency-ish order: code first (plugins, composer), then the ACF definitions content
|
|
11
12
|
// relies on, then content itself.
|
|
12
13
|
const PUSH_TARGETS = [
|
|
13
14
|
{ commandId: 'plugin:push', label: 'plugins' },
|
|
14
15
|
{ commandId: 'composer:push', label: 'composer' },
|
|
16
|
+
// Before ACF: a field group's location rule can target a Loopress post type or taxonomy.
|
|
17
|
+
{ commandId: 'cpt:push', label: 'post types' },
|
|
18
|
+
{ commandId: 'taxonomy:push', label: 'taxonomies' },
|
|
19
|
+
{ commandId: 'meta:push', label: 'post meta' },
|
|
20
|
+
{ commandId: 'role:push', label: 'roles' },
|
|
15
21
|
{ commandId: 'acf:push', label: 'ACF' },
|
|
16
22
|
{ commandId: 'api:push', label: 'API routes' },
|
|
17
23
|
{ commandId: 'hook:push', label: 'hooks' },
|
|
18
24
|
{ commandId: 'form:push', label: 'forms' },
|
|
25
|
+
// Before templates and pages, which can reference them (`wp:pattern`).
|
|
26
|
+
{ commandId: 'pattern:push', label: 'patterns' },
|
|
19
27
|
// Before pages: WordPress refuses a page whose `template` it doesn't know yet.
|
|
20
28
|
{ commandId: 'theme:template:push', label: 'templates and parts' },
|
|
21
29
|
{ commandId: 'page:push', label: 'pages' },
|
|
22
|
-
{ commandId: 'seo:push', label: 'SEO' },
|
|
23
30
|
{ commandId: 'menu:push', label: 'menus' },
|
|
24
31
|
{ commandId: 'option:push', label: 'options' },
|
|
25
32
|
{ commandId: 'snippet:push', label: 'snippets' },
|
|
26
33
|
];
|
|
27
34
|
export default class Push extends LoopressCommand {
|
|
28
|
-
static description = 'Push all local content, plugins, composer dependencies, ACF, API routes, hooks, forms, templates, pages,
|
|
35
|
+
static description = 'Push all local content, plugins, composer dependencies, post types, taxonomies, post meta, roles, ACF, API routes, hooks, forms, patterns, templates, pages, menus, options, and snippets, to WordPress';
|
|
29
36
|
static enableJsonFlag = true;
|
|
30
37
|
static examples = ['$ lps push', '$ lps push --env staging', '$ lps push --dry-run'];
|
|
31
38
|
static flags = {
|
|
32
39
|
...LoopressCommand.dryRunFlag,
|
|
33
40
|
...LoopressCommand.yesFlag,
|
|
41
|
+
overwrite: PushCommand.baseFlags.overwrite,
|
|
34
42
|
};
|
|
35
|
-
failedCount = 0;
|
|
36
43
|
async run() {
|
|
37
44
|
await this.guardProductionPush();
|
|
45
|
+
this.warnRemovedSeoModule();
|
|
38
46
|
const argv = this.buildArgv();
|
|
39
47
|
const results = [];
|
|
40
|
-
const
|
|
48
|
+
const skips = await this.skippedTargets();
|
|
41
49
|
for (const target of PUSH_TARGETS) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
const skip = skips.get(target.commandId);
|
|
51
|
+
if (skip) {
|
|
52
|
+
this.log(`\n→ ${target.label}: ${skip.note}`);
|
|
53
|
+
results.push({ label: target.label, status: skip.status });
|
|
45
54
|
continue;
|
|
46
55
|
}
|
|
47
56
|
this.log(`\n→ Pushing ${target.label}...`);
|
|
@@ -51,20 +60,16 @@ export default class Push extends LoopressCommand {
|
|
|
51
60
|
results.push({ label: target.label, status: 'pushed' });
|
|
52
61
|
}
|
|
53
62
|
catch (error) {
|
|
54
|
-
this.failedCount++;
|
|
55
63
|
const { message } = error;
|
|
56
64
|
this.log(`✗ ${target.label} failed: ${message}`);
|
|
57
65
|
results.push({ error: message, label: target.label, status: 'failed' });
|
|
66
|
+
// Stop here: later targets depend on earlier ones (hooks use what composer installs,
|
|
67
|
+
// pages need their templates), so pushing them anyway can ship a silently broken site.
|
|
68
|
+
const skipped = PUSH_TARGETS.slice(PUSH_TARGETS.indexOf(target) + 1).map((next) => next.label);
|
|
69
|
+
const skippedNote = skipped.length > 0 ? ` Not attempted: ${skipped.join(', ')}.` : '';
|
|
70
|
+
this.error(`${target.label} failed to push: ${message}.${skippedNote}`);
|
|
58
71
|
}
|
|
59
72
|
}
|
|
60
|
-
if (this.failedCount > 0) {
|
|
61
|
-
// Each failure's own reason, not just the count: under --json the per-resource lines above
|
|
62
|
-
// never reach stdout, and this message is all an MCP caller or a script gets to see.
|
|
63
|
-
const failures = results
|
|
64
|
-
.filter((result) => result.status === 'failed')
|
|
65
|
-
.map((result) => `${result.label}: ${result.error}`);
|
|
66
|
-
this.error(`${pluralize(this.failedCount, 'resource')} failed to push. ${failures.join('; ')}`);
|
|
67
|
-
}
|
|
68
73
|
this.log(this.dryRun ? '\nEvery resource would push.' : '\nAll resources pushed.');
|
|
69
74
|
return { results };
|
|
70
75
|
}
|
|
@@ -75,12 +80,15 @@ export default class Push extends LoopressCommand {
|
|
|
75
80
|
const argv = ['--env', this.siteConfig.name, '--yes'];
|
|
76
81
|
if (this.dryRun)
|
|
77
82
|
argv.push('--dry-run');
|
|
83
|
+
// Not implied by --yes: overwriting changes made outside Loopress is its own decision.
|
|
84
|
+
if (this.overwrite)
|
|
85
|
+
argv.push('--overwrite');
|
|
78
86
|
return argv;
|
|
79
87
|
}
|
|
80
88
|
// A composer.json that declares plugins is pushed whole by `plugin:push` (with its activation
|
|
81
89
|
// safety), so `composer:push` would only run Composer on the server a second time.
|
|
82
90
|
async composerDeclaresPlugins() {
|
|
83
|
-
const path =
|
|
91
|
+
const path = this.composerJsonPath();
|
|
84
92
|
if (!existsSync(path))
|
|
85
93
|
return false;
|
|
86
94
|
try {
|
|
@@ -92,6 +100,9 @@ export default class Push extends LoopressCommand {
|
|
|
92
100
|
return false;
|
|
93
101
|
}
|
|
94
102
|
}
|
|
103
|
+
composerJsonPath() {
|
|
104
|
+
return join(process.cwd(), this.rootDir, 'composer.json');
|
|
105
|
+
}
|
|
95
106
|
// Guards once here (shared with PushCommand, lib/push-command.ts) rather than once per
|
|
96
107
|
// delegated command.
|
|
97
108
|
async guardProductionPush() {
|
|
@@ -102,4 +113,25 @@ export default class Push extends LoopressCommand {
|
|
|
102
113
|
yes: this.yes,
|
|
103
114
|
});
|
|
104
115
|
}
|
|
116
|
+
// Unconfigured is not a failure: a project that doesn't manage plugins or composer through
|
|
117
|
+
// Loopress must not stop the run before anything else gets pushed.
|
|
118
|
+
async skippedTargets() {
|
|
119
|
+
const skips = new Map();
|
|
120
|
+
const notConfigured = { note: 'not configured, skipped', status: 'skipped' };
|
|
121
|
+
const hasComposerJson = existsSync(this.composerJsonPath());
|
|
122
|
+
const composerPushedByPlugins = await this.composerDeclaresPlugins();
|
|
123
|
+
if (composerPushedByPlugins)
|
|
124
|
+
skips.set('composer:push', { note: 'already pushed whole by plugins', status: 'pushed' });
|
|
125
|
+
else if (!hasComposerJson)
|
|
126
|
+
skips.set('composer:push', notConfigured);
|
|
127
|
+
// With a composer.json, plugins come from it only; otherwise from loopress.json.
|
|
128
|
+
const pluginsConfigured = hasComposerJson ? composerPushedByPlugins : Object.keys(this.localConfig.plugins ?? {}).length > 0;
|
|
129
|
+
if (!pluginsConfigured)
|
|
130
|
+
skips.set('plugin:push', notConfigured);
|
|
131
|
+
// The only push that fails on a missing directory (the others find 0 files), so a project
|
|
132
|
+
// that never used snippets would otherwise end every `lps push` on an ENOENT.
|
|
133
|
+
if (!existsSync(resolve(resolveResourceDir('snippets', this.localConfig))))
|
|
134
|
+
skips.set('snippet:push', notConfigured);
|
|
135
|
+
return skips;
|
|
136
|
+
}
|
|
105
137
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { resourceDiffCommand } from '../../lib/diff-command.js';
|
|
2
|
+
export default resourceDiffCommand('role', {
|
|
3
|
+
description: 'Show what differs in roles between your local roles/ files and a WordPress environment, or between two environments',
|
|
4
|
+
pathNoun: 'roles directory',
|
|
5
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { declaredListCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { ROLE_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
import { pluralize } from '../../utils/pluralize.js';
|
|
4
|
+
export default declaredListCommand(ROLE_SPEC, 'List the user roles on WordPress, where each comes from (Loopress, WordPress, a plugin) and how many users have it. Only Loopress ones are pulled and pushed.', (count) => pluralize(count, 'user'));
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { declaredPullCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { ROLE_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
export default declaredPullCommand(ROLE_SPEC, 'Pull the roles Loopress manages on WordPress into roles/<slug>.json files. ' +
|
|
4
|
+
'Built-in roles and roles created by a plugin are never pulled, see `lps role list`.');
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { declaredPushCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { ROLE_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
export default declaredPushCommand(ROLE_SPEC, 'Push local roles (roles/<slug>.json, a "name" and its "capabilities") to WordPress. ' +
|
|
4
|
+
'Create or update only, never deletes a role (use `lps role rm`), never changes which users have it.');
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { declaredRmCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { ROLE_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
export default declaredRmCommand(ROLE_SPEC, 'Stop registering a Loopress role on WordPress. Its users keep it in their profile, with no capability until the role is pushed again.', 'Its users keep it in their profile, with no capability until it is pushed again.');
|
|
@@ -28,7 +28,7 @@ export default class Push extends PushCommand {
|
|
|
28
28
|
this.log(`Snippets path: ${path}`);
|
|
29
29
|
const provider = getResourceStateProvider('snippet');
|
|
30
30
|
const beforeState = await this.captureBeforePushState(provider, path);
|
|
31
|
-
const snippets = await this.loadSnippets(path);
|
|
31
|
+
const snippets = this.withoutDuplicateIds(await this.loadSnippets(path));
|
|
32
32
|
this.log(`Found ${pluralize(snippets.length, 'snippet')} to push`);
|
|
33
33
|
const pushed = [];
|
|
34
34
|
await this.runPushTasks(snippets, (snippet) => snippet.name, async (snippet, task) => {
|
|
@@ -148,8 +148,11 @@ export default class Push extends PushCommand {
|
|
|
148
148
|
toPayload(snippet) {
|
|
149
149
|
return {
|
|
150
150
|
active: snippet.active,
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
// Only PHP files carry an editor-only `<?php` tag: an HTML snippet starting with an inline
|
|
152
|
+
// `<?php ... ?>` block is its real content and must reach WordPress untouched.
|
|
153
|
+
code: snippet.type === 'php' ? stripPhpOpeningTag(snippet.code) : snippet.code,
|
|
154
|
+
// The sidecar's description wins, so a pull then push never erases one written in wp-admin.
|
|
155
|
+
description: snippet.description ?? `Imported from ${snippet.path}`,
|
|
153
156
|
insertMethod: snippet.insertMethod,
|
|
154
157
|
location: snippet.location,
|
|
155
158
|
name: snippet.name,
|
|
@@ -159,4 +162,22 @@ export default class Push extends PushCommand {
|
|
|
159
162
|
type: snippet.type,
|
|
160
163
|
};
|
|
161
164
|
}
|
|
165
|
+
// Two files claiming the same id would each PUT over that id, the last one silently replacing
|
|
166
|
+
// the other on the site (seen after switching snippet provider, whose ids restart). Neither is
|
|
167
|
+
// pushed, and the push fails, until the files are fixed by hand.
|
|
168
|
+
withoutDuplicateIds(snippets) {
|
|
169
|
+
const pathsById = new Map();
|
|
170
|
+
for (const { id, path } of snippets) {
|
|
171
|
+
if (id !== undefined)
|
|
172
|
+
pathsById.set(id, [...(pathsById.get(id) ?? []), path]);
|
|
173
|
+
}
|
|
174
|
+
return snippets.filter((snippet) => {
|
|
175
|
+
const paths = snippet.id === undefined ? [] : pathsById.get(snippet.id);
|
|
176
|
+
if (paths.length < 2)
|
|
177
|
+
return true;
|
|
178
|
+
this.failedCount++;
|
|
179
|
+
this.warn(`Skipping "${snippet.path}": id ${snippet.id} is also used by ${paths.filter((p) => p !== snippet.path).join(', ')}.`);
|
|
180
|
+
return false;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
162
183
|
}
|
|
@@ -18,8 +18,7 @@ export default class Status extends Command {
|
|
|
18
18
|
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
19
|
};
|
|
20
20
|
run(): Promise<StatusResult>;
|
|
21
|
-
private
|
|
22
|
-
private
|
|
23
|
-
private reportPinnedProject;
|
|
21
|
+
private logTarget;
|
|
22
|
+
private report;
|
|
24
23
|
}
|
|
25
24
|
export {};
|
package/dist/commands/status.js
CHANGED
|
@@ -11,52 +11,26 @@ export default class Status extends Command {
|
|
|
11
11
|
};
|
|
12
12
|
async run() {
|
|
13
13
|
const { flags } = await this.parse(Status);
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
if (flags.env) {
|
|
17
|
-
info = this.reportEnvOverride(localConfig.projectId, flags.env);
|
|
18
|
-
}
|
|
19
|
-
else if (localConfig.projectId) {
|
|
20
|
-
info = this.reportPinnedProject(localConfig.projectId);
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
info = this.reportActiveProject();
|
|
24
|
-
}
|
|
14
|
+
const { projectId } = await readLocalConfig();
|
|
15
|
+
const info = this.report(projectId, flags.env);
|
|
25
16
|
this.log('');
|
|
26
17
|
this.log(`Config dir: ${this.config.configDir}`);
|
|
27
18
|
this.log(`Data dir: ${this.config.dataDir}`);
|
|
28
19
|
return { ...info, configDir: this.config.configDir, dataDir: this.config.dataDir };
|
|
29
20
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const project = env ? configManager.getCurrentProject() : undefined;
|
|
33
|
-
if (!env || !project) {
|
|
34
|
-
const note = 'No project configured. Run `lps project config` first.';
|
|
35
|
-
this.log(note);
|
|
36
|
-
return { note };
|
|
37
|
-
}
|
|
38
|
-
this.log(`Project: ${project.name} (${env.name})`);
|
|
21
|
+
logTarget(label, env) {
|
|
22
|
+
this.log(`Project: ${label}`);
|
|
39
23
|
this.log(`URL: ${env.url}`);
|
|
40
|
-
return { environment: env.name, project:
|
|
24
|
+
return { environment: env.name, project: label, url: env.url };
|
|
41
25
|
}
|
|
42
|
-
// Mirrors base.ts:resolveEnvironment
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (!project) {
|
|
26
|
+
// Mirrors base.ts:resolveEnvironment, reporting instead of failing where it can.
|
|
27
|
+
report(pinnedProjectId, envName) {
|
|
28
|
+
const projectId = pinnedProjectId ?? configManager.getCurrentProject()?.id;
|
|
29
|
+
if (!projectId) {
|
|
47
30
|
const note = 'No project configured. Run `lps project config` first.';
|
|
48
31
|
this.log(note);
|
|
49
32
|
return { note };
|
|
50
33
|
}
|
|
51
|
-
const env = project.environments[envName];
|
|
52
|
-
if (!env) {
|
|
53
|
-
this.error(`Environment "${envName}" not found in project "${project.name}". Available: ${Object.keys(project.environments).join(', ')}`);
|
|
54
|
-
}
|
|
55
|
-
this.log(`Project: ${project.name} (${env.name}, via --env)`);
|
|
56
|
-
this.log(`URL: ${env.url}`);
|
|
57
|
-
return { environment: env.name, project: `${project.name} (${env.name}, via --env)`, url: env.url };
|
|
58
|
-
}
|
|
59
|
-
reportPinnedProject(projectId) {
|
|
60
34
|
const project = configManager.getProject(projectId);
|
|
61
35
|
if (!project) {
|
|
62
36
|
const note = `loopress.json pins project "${projectId}", but it no longer exists. Run \`lps project config\` to configure it.`;
|
|
@@ -71,28 +45,22 @@ export default class Status extends Command {
|
|
|
71
45
|
this.log(note);
|
|
72
46
|
return { note, project: project.name };
|
|
73
47
|
}
|
|
74
|
-
if (
|
|
75
|
-
const env = project.environments[
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
48
|
+
if (envName) {
|
|
49
|
+
const env = project.environments[envName];
|
|
50
|
+
if (!env) {
|
|
51
|
+
this.error(`Environment "${envName}" not found in project "${project.name}". Available: ${envNames.join(', ')}`);
|
|
52
|
+
}
|
|
53
|
+
return this.logTarget(`${project.name} (${env.name}, via --env)`, env);
|
|
79
54
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
this.log(`Project: ${project.name} ${c('yellow', '(ambiguous)')}`);
|
|
55
|
+
const env = configManager.getDefaultEnvironment(projectId);
|
|
56
|
+
if (!env) {
|
|
57
|
+
const note = `"${project.name}" has no "local" environment, pass --env to pick one.`;
|
|
58
|
+
this.log(`Project: ${project.name} ${c('yellow', '(no default environment)')}`);
|
|
85
59
|
this.log(`Environments: ${envNames.join(', ')}`);
|
|
86
60
|
this.log('');
|
|
87
|
-
this.warn(
|
|
88
|
-
this.log('Run `lps project switch` to pick one before running commands here.');
|
|
89
|
-
if (current) {
|
|
90
|
-
this.log(`(Globally active project right now: "${current.name}")`);
|
|
91
|
-
}
|
|
61
|
+
this.warn(note);
|
|
92
62
|
return { environments: envNames, note, project: project.name };
|
|
93
63
|
}
|
|
94
|
-
this.
|
|
95
|
-
this.log(`URL: ${currentEnv.url}`);
|
|
96
|
-
return { environment: currentEnv.name, project: `${project.name} (${currentEnv.name})`, url: currentEnv.url };
|
|
64
|
+
return this.logTarget(`${project.name} (${env.name})`, env);
|
|
97
65
|
}
|
|
98
66
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { resourceDiffCommand } from '../../lib/diff-command.js';
|
|
2
|
+
export default resourceDiffCommand('taxonomy', {
|
|
3
|
+
description: 'Show what differs in taxonomies between your local taxonomies/ files and a WordPress environment, or between two environments',
|
|
4
|
+
pathNoun: 'taxonomies directory',
|
|
5
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { declaredListCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { TAXONOMY_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
import { pluralize } from '../../utils/pluralize.js';
|
|
4
|
+
export default declaredListCommand(TAXONOMY_SPEC, 'List the taxonomies registered on WordPress, where each comes from (Loopress, WordPress, ACF, CPT UI, a theme or plugin) ' +
|
|
5
|
+
'and the post types it attaches to. Only Loopress ones are pulled and pushed.', (count) => pluralize(count, 'term'));
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { declaredPullCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { TAXONOMY_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
export default declaredPullCommand(TAXONOMY_SPEC, 'Pull the taxonomies Loopress manages on WordPress into taxonomies/<slug>.json files, never their terms. ' +
|
|
4
|
+
'Taxonomies registered by a theme or another plugin are never pulled, see `lps taxonomy list`.');
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { declaredPushCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { TAXONOMY_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
export default declaredPushCommand(TAXONOMY_SPEC, 'Push local taxonomies (taxonomies/<slug>.json, register_taxonomy() arguments plus "object_type") to WordPress. ' +
|
|
4
|
+
'Create or update only, never deletes a taxonomy (use `lps taxonomy rm`), never touches its terms.');
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { declaredRmCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { TAXONOMY_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
export default declaredRmCommand(TAXONOMY_SPEC, 'Stop registering a Loopress taxonomy on WordPress. Its terms are kept in the database, hidden until the taxonomy is pushed again.', "Its terms 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('taxonomy', {
|
|
3
|
+
description: 'Restore taxonomies to a snapshot taken automatically before an earlier `lps taxonomy push`',
|
|
4
|
+
pathNoun: 'taxonomies directory',
|
|
5
|
+
});
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { PushCommand } from '../../../lib/push-command.js';
|
|
2
2
|
type PushResult = {
|
|
3
3
|
active?: boolean;
|
|
4
|
+
blockStyles: string[];
|
|
4
5
|
customized?: string[];
|
|
5
6
|
parts: string[];
|
|
6
7
|
status: 'dry-run' | 'success';
|
|
7
8
|
stylesheet?: string;
|
|
8
9
|
templates: string[];
|
|
10
|
+
unknownBlocks?: string[];
|
|
9
11
|
};
|
|
10
12
|
export default class Push extends PushCommand {
|
|
11
13
|
static description: string;
|
|
@@ -4,8 +4,9 @@ import { formatPageProblems } from '../../../utils/page-format.js';
|
|
|
4
4
|
import { pluralize } from '../../../utils/pluralize.js';
|
|
5
5
|
import { resolveResourceDir } from '../../../utils/resource-dirs.js';
|
|
6
6
|
import { CHILD_THEME_ENDPOINT, readLocalTemplates } from '../../../utils/template-format.js';
|
|
7
|
+
import { bundleThemeCss } from '../../../utils/theme-css.js';
|
|
7
8
|
export default class Push extends PushCommand {
|
|
8
|
-
static description = 'Push block templates (templates/<slug>.html)
|
|
9
|
+
static description = 'Push block templates (theme/templates/<slug>.html), template parts (theme/parts/<slug>.html) and the theme CSS (theme/css/style.css, theme/blocks/<namespace>/<block>.css, bundled with esbuild) as the files of a child theme of the active block theme, <parent>-loopress. The child mirrors the project: files removed locally are removed from it. It is never activated for you.';
|
|
9
10
|
static enableJsonFlag = true;
|
|
10
11
|
static examples = ['$ lps theme template push', '$ lps theme template push --dry-run'];
|
|
11
12
|
static flags = {
|
|
@@ -19,17 +20,24 @@ export default class Push extends PushCommand {
|
|
|
19
20
|
this.log(`Pushing templates and parts to ${this.siteConfig.url}`);
|
|
20
21
|
this.log(`Templates path: ${templatesPath}, parts path: ${partsPath}`);
|
|
21
22
|
const { parts, problems, templates } = await readLocalTemplates(templatesPath, partsPath);
|
|
23
|
+
const { css, problems: cssProblems } = await bundleThemeCss(resolveResourceDir('css', this.localConfig), resolveResourceDir('blockCss', this.localConfig));
|
|
24
|
+
problems.push(...cssProblems);
|
|
22
25
|
if (problems.length > 0) {
|
|
23
26
|
this.error(`Nothing was pushed, fix these files first:\n ${formatPageProblems(problems)}`);
|
|
24
27
|
}
|
|
25
|
-
const names = {
|
|
26
|
-
|
|
28
|
+
const names = {
|
|
29
|
+
blockStyles: css.blocks.map((block) => block.name),
|
|
30
|
+
parts: parts.map((part) => part.slug),
|
|
31
|
+
templates: templates.map((template) => template.slug),
|
|
32
|
+
};
|
|
33
|
+
this.log(`Found ${pluralize(templates.length, 'template')}, ${pluralize(parts.length, 'part')}, ` +
|
|
34
|
+
`${css.style ? 'a global stylesheet' : 'no global stylesheet'} and ${pluralize(css.blocks.length, 'block stylesheet')}`);
|
|
27
35
|
if (this.dryRun) {
|
|
28
36
|
await this.previewRemote(getResourceStateProvider('template'), templatesPath);
|
|
29
37
|
this.log('[dry-run] Would rewrite the child theme with them.');
|
|
30
38
|
return { ...names, status: 'dry-run' };
|
|
31
39
|
}
|
|
32
|
-
const child = await this.wp.put(CHILD_THEME_ENDPOINT, { parts, templates });
|
|
40
|
+
const child = await this.wp.put(CHILD_THEME_ENDPOINT, { ...css, parts, templates });
|
|
33
41
|
await this.recordSuccess();
|
|
34
42
|
this.log(`Child theme ${child.stylesheet} written.`);
|
|
35
43
|
if (!child.active) {
|
|
@@ -39,6 +47,19 @@ export default class Push extends PushCommand {
|
|
|
39
47
|
this.warn(`Edited in the Site Editor, their database copy still wins over the pushed file: ${child.customized.join(', ')}. ` +
|
|
40
48
|
'Clear their customizations in Appearance > Editor to use the pushed version.');
|
|
41
49
|
}
|
|
42
|
-
|
|
50
|
+
// A warning, never a refusal: the block may come from a plugin active on another environment.
|
|
51
|
+
const unknownBlocks = child.unknownBlocks ?? [];
|
|
52
|
+
if (unknownBlocks.length > 0) {
|
|
53
|
+
this.warn(`No block registered on this site under these names, their CSS will never load: ${unknownBlocks.join(', ')}. ` +
|
|
54
|
+
'Check the file name in theme/blocks/, or activate the plugin that provides the block.');
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
...names,
|
|
58
|
+
active: child.active,
|
|
59
|
+
customized: child.customized,
|
|
60
|
+
status: 'success',
|
|
61
|
+
stylesheet: child.stylesheet,
|
|
62
|
+
unknownBlocks,
|
|
63
|
+
};
|
|
43
64
|
}
|
|
44
65
|
}
|
|
@@ -16,8 +16,13 @@ export default class Pull extends LoopressCommand {
|
|
|
16
16
|
const { url } = this.siteConfig;
|
|
17
17
|
this.log(`Pulling themes from ${url}`);
|
|
18
18
|
const raw = await this.wp.get('wp/v2/themes');
|
|
19
|
+
// A theme with no Version header can't be pinned, and `theme version push` would then fail on
|
|
20
|
+
// it: the Loopress child theme (`<parent>-loopress`, written by `theme template push`) is one.
|
|
19
21
|
const installed = parseInstalledThemes(raw);
|
|
20
|
-
const
|
|
22
|
+
const unversioned = installed.filter((t) => t.version === '').map((t) => t.slug);
|
|
23
|
+
const incoming = Object.fromEntries(installed.filter((t) => t.version !== '').map((t) => [t.slug, t.version]));
|
|
24
|
+
if (unversioned.length > 0)
|
|
25
|
+
this.log(` - Skipped: ${unversioned.join(', ')} (no version to pin)`);
|
|
21
26
|
// A composer.json is authoritative over loopress.json: pin the live versions there instead.
|
|
22
27
|
const composerJsonPath = join(process.cwd(), this.rootDir, 'composer.json');
|
|
23
28
|
if (existsSync(composerJsonPath)) {
|
|
@@ -8,15 +8,15 @@ export declare class ProjectConfigManager {
|
|
|
8
8
|
id: string;
|
|
9
9
|
});
|
|
10
10
|
getConfigFilePath(): string;
|
|
11
|
-
getCurrentEnv(): EnvironmentConfig | null;
|
|
12
11
|
getCurrentProject(): null | (ProjectConfig & {
|
|
13
12
|
id: string;
|
|
14
13
|
});
|
|
14
|
+
getDefaultEnvironment(projectId: string): EnvironmentConfig | null;
|
|
15
15
|
getEnvironment(projectId: string, envName: string): EnvironmentConfig | null;
|
|
16
16
|
getProject(id: string): null | ProjectConfig;
|
|
17
17
|
isTelemetryDisabled(): boolean;
|
|
18
18
|
listEnvironments(projectId: string): Array<EnvironmentConfig & {
|
|
19
|
-
|
|
19
|
+
isDefault: boolean;
|
|
20
20
|
}>;
|
|
21
21
|
listProjects(): Array<ProjectConfig & {
|
|
22
22
|
id: string;
|
|
@@ -26,7 +26,7 @@ export declare class ProjectConfigManager {
|
|
|
26
26
|
removeEnvironment(projectId: string, envName: string): void;
|
|
27
27
|
removeProject(id: string): void;
|
|
28
28
|
setConfigDir(configDir: string): void;
|
|
29
|
-
setCurrent(projectId: string
|
|
29
|
+
setCurrent(projectId: string): void;
|
|
30
30
|
setEnvironment(projectId: string, envName: string, env: EnvironmentConfig): void;
|
|
31
31
|
setEnvironmentApiId(projectId: string, envName: string, apiEnvironmentId: string): void;
|
|
32
32
|
setProject(id: string, project: ProjectConfig): void;
|