@loopress/cli 0.27.0 → 0.29.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 +448 -189
- package/bin/dev.js +0 -0
- package/dist/commands/acf/pull.d.ts +9 -1
- package/dist/commands/acf/pull.js +2 -0
- package/dist/commands/acf/push.d.ts +9 -1
- package/dist/commands/acf/push.js +3 -1
- package/dist/commands/api/publish.js +1 -1
- package/dist/commands/app/pull.js +17 -4
- package/dist/commands/app/push.d.ts +3 -0
- package/dist/commands/app/push.js +30 -6
- package/dist/commands/composer/push.js +3 -21
- package/dist/commands/diff.js +1 -1
- package/dist/commands/form/pull.d.ts +9 -1
- package/dist/commands/form/pull.js +3 -1
- package/dist/commands/form/push.d.ts +7 -1
- package/dist/commands/form/push.js +3 -1
- package/dist/commands/init.js +6 -36
- package/dist/commands/menu/pull.d.ts +7 -1
- package/dist/commands/menu/pull.js +2 -0
- package/dist/commands/menu/push.d.ts +7 -1
- package/dist/commands/menu/push.js +3 -1
- package/dist/commands/option/pull.js +4 -4
- package/dist/commands/option/push.js +2 -0
- package/dist/commands/page/push.js +3 -0
- package/dist/commands/plugin/add.js +6 -3
- package/dist/commands/plugin/audit.js +4 -5
- package/dist/commands/plugin/pull.d.ts +7 -1
- package/dist/commands/plugin/pull.js +68 -14
- package/dist/commands/plugin/push.d.ts +5 -1
- package/dist/commands/plugin/push.js +92 -38
- package/dist/commands/plugin/status.d.ts +1 -0
- package/dist/commands/plugin/status.js +21 -2
- package/dist/commands/project/config.js +12 -8
- package/dist/commands/promote.d.ts +2 -0
- package/dist/commands/promote.js +69 -8
- package/dist/commands/pull.js +2 -2
- package/dist/commands/push.d.ts +1 -0
- package/dist/commands/push.js +40 -5
- package/dist/commands/seo/pull.d.ts +8 -1
- package/dist/commands/seo/pull.js +2 -0
- package/dist/commands/seo/push.d.ts +7 -1
- package/dist/commands/seo/push.js +3 -1
- package/dist/commands/snippet/publish.js +1 -1
- package/dist/commands/snippet/pull.js +1 -1
- package/dist/commands/snippet/push.js +1 -1
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +4 -4
- package/dist/commands/theme/pull.d.ts +6 -8
- package/dist/commands/theme/pull.js +30 -26
- package/dist/commands/theme/push.d.ts +8 -9
- package/dist/commands/theme/push.js +54 -111
- package/dist/commands/theme/style/diff.d.ts +2 -0
- package/dist/commands/{theme-styles → theme/style}/diff.js +2 -1
- package/dist/commands/{theme-styles → theme/style}/pull.d.ts +8 -2
- package/dist/commands/{theme-styles → theme/style}/pull.js +6 -4
- package/dist/commands/{theme-styles → theme/style}/push.d.ts +9 -2
- package/dist/commands/{theme-styles → theme/style}/push.js +9 -6
- package/dist/commands/theme/style/rollback.d.ts +2 -0
- package/dist/commands/theme/style/rollback.js +6 -0
- package/dist/commands/theme/template/diff.d.ts +10 -0
- package/dist/commands/theme/template/diff.js +13 -0
- package/dist/commands/theme/template/list.d.ts +13 -0
- package/dist/commands/theme/template/list.js +23 -0
- package/dist/commands/theme/template/push.d.ts +20 -0
- package/dist/commands/theme/template/push.js +44 -0
- package/dist/commands/theme/version/pull.d.ts +23 -0
- package/dist/commands/theme/version/pull.js +43 -0
- package/dist/commands/theme/version/push.d.ts +22 -0
- package/dist/commands/theme/version/push.js +123 -0
- package/dist/hooks/finally.d.ts +2 -1
- package/dist/hooks/finally.js +27 -4
- package/dist/lib/api-client.js +3 -1
- package/dist/lib/app-build.d.ts +5 -0
- package/dist/lib/app-build.js +69 -0
- package/dist/lib/app-manifest.d.ts +4 -0
- package/dist/lib/app-manifest.js +10 -2
- package/dist/lib/base.d.ts +1 -0
- package/dist/lib/base.js +14 -2
- package/dist/lib/bootstrap-full-install.js +45 -14
- package/dist/lib/dev-watch.d.ts +1 -1
- package/dist/lib/dev-watch.js +3 -1
- package/dist/lib/diff-command.d.ts +1 -0
- package/dist/lib/diff-command.js +6 -4
- package/dist/lib/diff-state.js +1 -0
- package/dist/lib/github-release.js +0 -1
- package/dist/lib/local-callback-server.d.ts +12 -6
- package/dist/lib/local-callback-server.js +21 -22
- package/dist/lib/php-files-command.js +3 -1
- package/dist/lib/push-command.d.ts +5 -0
- package/dist/lib/push-command.js +30 -8
- package/dist/lib/resource-state.js +47 -4
- package/dist/lib/rollback-command.d.ts +1 -0
- package/dist/lib/rollback-command.js +8 -6
- package/dist/lib/rotate-app-password.js +2 -2
- package/dist/lib/snapshot-store.js +2 -2
- package/dist/lib/validate-local.js +6 -0
- package/dist/lib/wp-authorize-flow.d.ts +10 -17
- package/dist/lib/wp-authorize-flow.js +24 -28
- package/dist/lib/wp-client.d.ts +4 -1
- package/dist/lib/wp-client.js +25 -1
- package/dist/lib/wp-site-diagnostic.js +37 -0
- package/dist/types/plugin.d.ts +8 -1
- package/dist/types/plugin.js +4 -1
- package/dist/types/project-config.generated.d.ts +19 -2
- package/dist/utils/composer.d.ts +16 -0
- package/dist/utils/composer.js +144 -0
- package/dist/utils/option-format.d.ts +3 -1
- package/dist/utils/option-format.js +14 -3
- package/dist/utils/page-format.d.ts +12 -0
- package/dist/utils/page-format.js +40 -28
- package/dist/utils/plugin-sync.js +1 -1
- package/dist/utils/plugins.d.ts +9 -4
- package/dist/utils/plugins.js +16 -7
- package/dist/utils/resource-dirs.d.ts +2 -0
- package/dist/utils/resource-dirs.js +6 -2
- package/dist/utils/seo-format.d.ts +2 -2
- package/dist/utils/template-format.d.ts +31 -0
- package/dist/utils/template-format.js +43 -0
- package/oclif.manifest.json +841 -63
- package/package.json +24 -17
- package/schemas/project-config.schema.json +25 -4
- package/dist/commands/theme-styles/diff.d.ts +0 -2
- package/dist/commands/theme-styles/rollback.d.ts +0 -2
- package/dist/commands/theme-styles/rollback.js +0 -5
package/dist/commands/promote.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { confirm } from '@inquirer/prompts';
|
|
2
2
|
import { Args, Command } from '@oclif/core';
|
|
3
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins -- stable since 22.3, works unflagged on 22.0+
|
|
4
|
+
import { cp, mkdtemp, rm } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { basename, isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
7
|
import { configManager } from '../config/project-config.manager.js';
|
|
4
8
|
import { LoopressCommand } from '../lib/base.js';
|
|
5
9
|
import { isInteractive } from '../lib/interactive.js';
|
|
6
10
|
import { stdoutToStderr } from '../lib/json-delegation.js';
|
|
7
11
|
import { readLocalConfig } from '../utils/loopress-config.js';
|
|
12
|
+
import { resolveResourceDir, RESOURCE_DIR_DEFAULTS } from '../utils/resource-dirs.js';
|
|
8
13
|
export default class Promote extends Command {
|
|
9
14
|
static args = {
|
|
10
15
|
from: Args.string({ description: 'Environment to copy the configuration from', required: true }),
|
|
11
16
|
to: Args.string({ description: 'Environment to copy the configuration to', required: true }),
|
|
12
17
|
};
|
|
13
|
-
static description = 'Copy every tracked resource from one environment to another by pulling from <from> then pushing to <to>. Local tracked files are overwritten with <from> in the process.';
|
|
18
|
+
static description = 'Copy every tracked resource from one environment to another by pulling from <from> then pushing to <to>. Local tracked files are overwritten with <from> in the process. With --dry-run, <from> is pulled into a throwaway copy of the project instead, and the push is only previewed.';
|
|
14
19
|
static enableJsonFlag = true;
|
|
15
20
|
static examples = ['$ lps promote staging production', '$ lps promote production staging --dry-run'];
|
|
16
21
|
static flags = {
|
|
@@ -29,19 +34,55 @@ export default class Promote extends Command {
|
|
|
29
34
|
}
|
|
30
35
|
if (!dryRun)
|
|
31
36
|
await this.confirmPromotion(from.name, to.name, to.url, yes);
|
|
32
|
-
|
|
37
|
+
// A dry-run pull writes nothing, so a dry-run push right after would preview the current
|
|
38
|
+
// local files instead of <from>. Instead, really pull into a throwaway copy of the project
|
|
39
|
+
// and dry-run the push from there: the preview is what a real promotion would push, and the
|
|
40
|
+
// user's own files stay untouched.
|
|
41
|
+
const originalCwd = process.cwd();
|
|
42
|
+
const scratch = dryRun ? await this.enterScratchCopy(originalCwd) : undefined;
|
|
43
|
+
// The delegated pull is real, so without this its auto-rotation could revoke a stale app
|
|
44
|
+
// password during what the user asked to be a preview (see LoopressCommand.maybeAutoRotate).
|
|
45
|
+
const previousNoRotate = process.env.LOOPRESS_NO_AUTO_ROTATE;
|
|
46
|
+
if (scratch)
|
|
47
|
+
process.env.LOOPRESS_NO_AUTO_ROTATE = '1';
|
|
33
48
|
try {
|
|
34
|
-
|
|
49
|
+
this.log(`\n=== Pulling from ${from.name} (${from.url}) ===`);
|
|
50
|
+
try {
|
|
51
|
+
await stdoutToStderr(this.jsonEnabled(), async () => this.config.runCommand('pull', this.delegateArgv(from.name, false)));
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// A partial pull must never be pushed onward: stop before touching <to>.
|
|
55
|
+
this.error(`Pull from ${from.name} failed, ${to.name} left untouched: ${error.message}`);
|
|
56
|
+
}
|
|
57
|
+
this.log(`\n=== Pushing to ${to.name} (${to.url}) ===`);
|
|
58
|
+
await stdoutToStderr(this.jsonEnabled(), async () => this.config.runCommand('push', this.delegateArgv(to.name, dryRun)));
|
|
35
59
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
60
|
+
finally {
|
|
61
|
+
if (scratch) {
|
|
62
|
+
if (previousNoRotate === undefined)
|
|
63
|
+
delete process.env.LOOPRESS_NO_AUTO_ROTATE;
|
|
64
|
+
else
|
|
65
|
+
process.env.LOOPRESS_NO_AUTO_ROTATE = previousNoRotate;
|
|
66
|
+
process.chdir(originalCwd);
|
|
67
|
+
await rm(scratch, { force: true, recursive: true });
|
|
68
|
+
}
|
|
39
69
|
}
|
|
40
|
-
this.log(`\n=== Pushing to ${to.name} (${to.url}) ===`);
|
|
41
|
-
await stdoutToStderr(this.jsonEnabled(), async () => this.config.runCommand('push', this.delegateArgv(to.name, dryRun)));
|
|
42
70
|
this.log(dryRun ? `\n[dry-run] ${from.name} would be promoted to ${to.name}.` : `\n${from.name} promoted to ${to.name}.`);
|
|
43
71
|
return { from: from.name, status: dryRun ? 'dry-run' : 'promoted', to: to.name };
|
|
44
72
|
}
|
|
73
|
+
// The scratch pull writes wherever loopress.json points. An absolute or `../` rootDir or
|
|
74
|
+
// resource dir would land outside the copy, in the user's real files, during a dry run.
|
|
75
|
+
async assertDirsInsideProject(projectDir) {
|
|
76
|
+
const localConfig = await readLocalConfig();
|
|
77
|
+
const dirs = [localConfig.rootDir ?? '.', ...Object.keys(RESOURCE_DIR_DEFAULTS).map((kind) => resolveResourceDir(kind, localConfig))];
|
|
78
|
+
const outside = dirs.find((dir) => {
|
|
79
|
+
const rel = relative(projectDir, resolve(projectDir, dir));
|
|
80
|
+
return rel.startsWith('..') || isAbsolute(rel);
|
|
81
|
+
});
|
|
82
|
+
if (outside !== undefined) {
|
|
83
|
+
this.error(`--dry-run can't preview safely: "${outside}" in loopress.json is outside the project. Run \`lps diff --env <from> --against <to>\` instead.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
45
86
|
// Deliberately not `guardProductionPush`: promote overwrites local tracked files for every
|
|
46
87
|
// target, not just production, so it needs its own confirmation regardless of the name. That
|
|
47
88
|
// prompt also calls out production when relevant, making a second production-only guard
|
|
@@ -70,6 +111,26 @@ export default class Promote extends Command {
|
|
|
70
111
|
argv.push('--dry-run');
|
|
71
112
|
return argv;
|
|
72
113
|
}
|
|
114
|
+
// Copies the whole project (not just what `pull` rewrites): push also reads files pull never
|
|
115
|
+
// touches, and a real promotion pushes those as they are locally. node_modules and .git are
|
|
116
|
+
// never read by push. Returns the scratch root, cwd is left inside the copy.
|
|
117
|
+
async enterScratchCopy(projectDir) {
|
|
118
|
+
await this.assertDirsInsideProject(projectDir);
|
|
119
|
+
const scratch = await mkdtemp(join(tmpdir(), 'lps-promote-'));
|
|
120
|
+
try {
|
|
121
|
+
const copy = join(scratch, basename(projectDir));
|
|
122
|
+
await cp(projectDir, copy, {
|
|
123
|
+
filter: (source) => !['.git', 'node_modules'].includes(basename(source)),
|
|
124
|
+
recursive: true,
|
|
125
|
+
});
|
|
126
|
+
process.chdir(copy);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
await rm(scratch, { force: true, recursive: true });
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
return scratch;
|
|
133
|
+
}
|
|
73
134
|
requireEnvironment(environments, projectName, name) {
|
|
74
135
|
const env = environments[name];
|
|
75
136
|
if (!env) {
|
package/dist/commands/pull.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LoopressCommand } from '../lib/base.js';
|
|
2
2
|
import { stdoutToStderr } from '../lib/json-delegation.js';
|
|
3
3
|
import { pluralize } from '../utils/pluralize.js';
|
|
4
|
-
// composer before plugins: `plugin:pull`
|
|
5
|
-
//
|
|
4
|
+
// composer before plugins: when a composer.json exists, `plugin:pull` merges the live plugins
|
|
5
|
+
// into it, so it must run on the freshly pulled one or `composer:pull` would overwrite its work. The rest write to independent
|
|
6
6
|
// directories and their order does not matter, it mirrors `lps push` for familiarity.
|
|
7
7
|
const PULL_TARGETS = [
|
|
8
8
|
{ commandId: 'composer:pull', label: 'composer' },
|
package/dist/commands/push.d.ts
CHANGED
package/dist/commands/push.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
1
4
|
import { LoopressCommand } from '../lib/base.js';
|
|
2
5
|
import { guardProductionPush } from '../lib/guard-production-push.js';
|
|
3
6
|
import { stdoutToStderr } from '../lib/json-delegation.js';
|
|
7
|
+
import { isLocalEnvironment } from '../lib/wp-client.js';
|
|
8
|
+
import { wpackagistRequire } from '../utils/composer.js';
|
|
4
9
|
import { pluralize } from '../utils/pluralize.js';
|
|
5
10
|
// Dependency-ish order: code first (plugins, composer), then the ACF definitions content
|
|
6
11
|
// relies on, then content itself.
|
|
@@ -11,6 +16,8 @@ const PUSH_TARGETS = [
|
|
|
11
16
|
{ commandId: 'api:push', label: 'API routes' },
|
|
12
17
|
{ commandId: 'hook:push', label: 'hooks' },
|
|
13
18
|
{ commandId: 'form:push', label: 'forms' },
|
|
19
|
+
// Before pages: WordPress refuses a page whose `template` it doesn't know yet.
|
|
20
|
+
{ commandId: 'theme:template:push', label: 'templates and parts' },
|
|
14
21
|
{ commandId: 'page:push', label: 'pages' },
|
|
15
22
|
{ commandId: 'seo:push', label: 'SEO' },
|
|
16
23
|
{ commandId: 'menu:push', label: 'menus' },
|
|
@@ -18,7 +25,7 @@ const PUSH_TARGETS = [
|
|
|
18
25
|
{ commandId: 'snippet:push', label: 'snippets' },
|
|
19
26
|
];
|
|
20
27
|
export default class Push extends LoopressCommand {
|
|
21
|
-
static description = 'Push all local content, plugins, composer dependencies, ACF, API routes, hooks, forms, pages, SEO, menus, options, and snippets, to WordPress';
|
|
28
|
+
static description = 'Push all local content, plugins, composer dependencies, ACF, API routes, hooks, forms, templates, pages, SEO, menus, options, and snippets, to WordPress';
|
|
22
29
|
static enableJsonFlag = true;
|
|
23
30
|
static examples = ['$ lps push', '$ lps push --env staging', '$ lps push --dry-run'];
|
|
24
31
|
static flags = {
|
|
@@ -30,11 +37,17 @@ export default class Push extends LoopressCommand {
|
|
|
30
37
|
await this.guardProductionPush();
|
|
31
38
|
const argv = this.buildArgv();
|
|
32
39
|
const results = [];
|
|
40
|
+
const composerPushedByPlugins = await this.composerDeclaresPlugins();
|
|
33
41
|
for (const target of PUSH_TARGETS) {
|
|
42
|
+
if (composerPushedByPlugins && target.commandId === 'composer:push') {
|
|
43
|
+
this.log('\n→ composer: already pushed whole by plugins');
|
|
44
|
+
results.push({ label: target.label, status: 'pushed' });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
34
47
|
this.log(`\n→ Pushing ${target.label}...`);
|
|
35
48
|
try {
|
|
36
49
|
await stdoutToStderr(this.jsonEnabled(), async () => this.config.runCommand(target.commandId, argv));
|
|
37
|
-
this.log(`✓ ${target.label} pushed`);
|
|
50
|
+
this.log(this.dryRun ? `✓ ${target.label} would push` : `✓ ${target.label} pushed`);
|
|
38
51
|
results.push({ label: target.label, status: 'pushed' });
|
|
39
52
|
}
|
|
40
53
|
catch (error) {
|
|
@@ -47,10 +60,12 @@ export default class Push extends LoopressCommand {
|
|
|
47
60
|
if (this.failedCount > 0) {
|
|
48
61
|
// Each failure's own reason, not just the count: under --json the per-resource lines above
|
|
49
62
|
// never reach stdout, and this message is all an MCP caller or a script gets to see.
|
|
50
|
-
const failures = results
|
|
63
|
+
const failures = results
|
|
64
|
+
.filter((result) => result.status === 'failed')
|
|
65
|
+
.map((result) => `${result.label}: ${result.error}`);
|
|
51
66
|
this.error(`${pluralize(this.failedCount, 'resource')} failed to push. ${failures.join('; ')}`);
|
|
52
67
|
}
|
|
53
|
-
this.log('\nAll resources pushed.');
|
|
68
|
+
this.log(this.dryRun ? '\nEvery resource would push.' : '\nAll resources pushed.');
|
|
54
69
|
return { results };
|
|
55
70
|
}
|
|
56
71
|
// Every delegated push always gets --yes: either this command's own guard already confirmed
|
|
@@ -62,9 +77,29 @@ export default class Push extends LoopressCommand {
|
|
|
62
77
|
argv.push('--dry-run');
|
|
63
78
|
return argv;
|
|
64
79
|
}
|
|
80
|
+
// A composer.json that declares plugins is pushed whole by `plugin:push` (with its activation
|
|
81
|
+
// safety), so `composer:push` would only run Composer on the server a second time.
|
|
82
|
+
async composerDeclaresPlugins() {
|
|
83
|
+
const path = join(process.cwd(), this.rootDir, 'composer.json');
|
|
84
|
+
if (!existsSync(path))
|
|
85
|
+
return false;
|
|
86
|
+
try {
|
|
87
|
+
const composerJson = JSON.parse(await readFile(path, 'utf8'));
|
|
88
|
+
return Object.keys(wpackagistRequire(composerJson, 'plugin', isLocalEnvironment(this.siteConfig.name))).length > 0;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Unreadable: let both commands run and report the parse error themselves.
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
65
95
|
// Guards once here (shared with PushCommand, lib/push-command.ts) rather than once per
|
|
66
96
|
// delegated command.
|
|
67
97
|
async guardProductionPush() {
|
|
68
|
-
await guardProductionPush({
|
|
98
|
+
await guardProductionPush({
|
|
99
|
+
dryRun: this.dryRun,
|
|
100
|
+
error: (message) => this.error(message),
|
|
101
|
+
siteConfig: this.siteConfig,
|
|
102
|
+
yes: this.yes,
|
|
103
|
+
});
|
|
69
104
|
}
|
|
70
105
|
}
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
type SeoPullResult = {
|
|
3
|
+
path: string;
|
|
4
|
+
postTypes: string[];
|
|
5
|
+
status: 'dry-run' | 'success';
|
|
6
|
+
};
|
|
2
7
|
export default class Pull extends LoopressCommand {
|
|
3
8
|
static args: {
|
|
4
9
|
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
10
|
};
|
|
6
11
|
static description: string;
|
|
12
|
+
static enableJsonFlag: boolean;
|
|
7
13
|
static examples: string[];
|
|
8
14
|
static flags: {
|
|
9
15
|
'post-type': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
16
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
17
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
18
|
};
|
|
13
|
-
run(): Promise<
|
|
19
|
+
run(): Promise<SeoPullResult>;
|
|
14
20
|
private pullPostMeta;
|
|
15
21
|
private pullRedirects;
|
|
16
22
|
private pullSettings;
|
|
17
23
|
}
|
|
24
|
+
export {};
|
|
@@ -9,6 +9,7 @@ export default class Pull extends LoopressCommand {
|
|
|
9
9
|
path: Args.string({ description: 'Path to SEO directory (overrides project config)' }),
|
|
10
10
|
};
|
|
11
11
|
static description = 'Pull SEO settings, post meta, and (if supported) redirects from WordPress';
|
|
12
|
+
static enableJsonFlag = true;
|
|
12
13
|
static examples = ['$ lps seo pull', '$ lps seo pull --post-type post --post-type page'];
|
|
13
14
|
static flags = {
|
|
14
15
|
...LoopressCommand.dryRunFlag,
|
|
@@ -27,6 +28,7 @@ export default class Pull extends LoopressCommand {
|
|
|
27
28
|
await this.pullPostMeta(postType, path);
|
|
28
29
|
}
|
|
29
30
|
await this.pullRedirects(path);
|
|
31
|
+
return { path, postTypes, status: this.dryRun ? 'dry-run' : 'success' };
|
|
30
32
|
}
|
|
31
33
|
async pullPostMeta(postType, basePath) {
|
|
32
34
|
const dir = join(basePath, 'post-meta', postType);
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { PushCommand } from '../../lib/push-command.js';
|
|
2
|
+
type SeoPushResult = {
|
|
3
|
+
path: string;
|
|
4
|
+
status: 'dry-run' | 'success';
|
|
5
|
+
};
|
|
2
6
|
export default class Push extends PushCommand {
|
|
3
7
|
static args: {
|
|
4
8
|
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
9
|
};
|
|
6
10
|
static description: string;
|
|
11
|
+
static enableJsonFlag: boolean;
|
|
7
12
|
static examples: string[];
|
|
8
13
|
static flags: {
|
|
9
14
|
'allow-external-redirects': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -11,7 +16,7 @@ export default class Push extends PushCommand {
|
|
|
11
16
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
17
|
};
|
|
13
18
|
private allowExternalRedirects;
|
|
14
|
-
run(): Promise<
|
|
19
|
+
run(): Promise<SeoPushResult>;
|
|
15
20
|
private currentPostMetaRevision;
|
|
16
21
|
private jsonFilesIn;
|
|
17
22
|
private pushPostMeta;
|
|
@@ -21,3 +26,4 @@ export default class Push extends PushCommand {
|
|
|
21
26
|
private pushSettings;
|
|
22
27
|
private renameToCanonical;
|
|
23
28
|
}
|
|
29
|
+
export {};
|
|
@@ -13,6 +13,7 @@ export default class Push extends PushCommand {
|
|
|
13
13
|
path: Args.string({ description: 'Path to SEO directory (overrides project config)' }),
|
|
14
14
|
};
|
|
15
15
|
static description = 'Push SEO settings, post meta, and redirects to WordPress. Local redirect files created remotely are renamed on disk to the `<id>-<slug>` convention. Fails clearly per file if the active SEO plugin does not support redirects.';
|
|
16
|
+
static enableJsonFlag = true;
|
|
16
17
|
static examples = ['$ lps seo push', '$ lps seo push --allow-external-redirects'];
|
|
17
18
|
static flags = {
|
|
18
19
|
...PushCommand.dryRunFlag,
|
|
@@ -40,9 +41,10 @@ export default class Push extends PushCommand {
|
|
|
40
41
|
this.error(`${pluralize(this.failedCount, 'SEO item')} failed to push.`);
|
|
41
42
|
}
|
|
42
43
|
if (this.dryRun)
|
|
43
|
-
return;
|
|
44
|
+
return { path, status: 'dry-run' };
|
|
44
45
|
await this.recordSuccess();
|
|
45
46
|
this.log('All SEO configuration pushed.');
|
|
47
|
+
return { path, status: 'success' };
|
|
46
48
|
}
|
|
47
49
|
async currentPostMetaRevision(postType, slug) {
|
|
48
50
|
try {
|
|
@@ -14,7 +14,7 @@ export default class Publish extends Command {
|
|
|
14
14
|
path: Args.string({ description: 'Path to snippets directory (overrides project config)' }),
|
|
15
15
|
};
|
|
16
16
|
static description = 'Publish snippets to your Loopress account so they can be deployed to other projects. Does not touch any WordPress site.';
|
|
17
|
-
static examples = ['$ lps snippet publish', '$ lps snippet publish
|
|
17
|
+
static examples = ['$ lps snippet publish', '$ lps snippet publish ./snippets'];
|
|
18
18
|
async run() {
|
|
19
19
|
const { args } = await this.parse(Publish);
|
|
20
20
|
const { apiProjectId, localConfig, token } = await resolveLinkedProject((message) => this.error(message));
|
|
@@ -19,7 +19,7 @@ export default class Pull extends LoopressCommand {
|
|
|
19
19
|
};
|
|
20
20
|
static description = 'Pull snippets from WordPress';
|
|
21
21
|
static enableJsonFlag = true;
|
|
22
|
-
static examples = ['$ lps snippet pull', '$ lps snippet pull
|
|
22
|
+
static examples = ['$ lps snippet pull', '$ lps snippet pull ./snippets'];
|
|
23
23
|
static flags = {
|
|
24
24
|
...LoopressCommand.dryRunFlag,
|
|
25
25
|
...LoopressCommand.yesFlag,
|
|
@@ -15,7 +15,7 @@ export default class Push extends PushCommand {
|
|
|
15
15
|
};
|
|
16
16
|
static description = 'Push snippets to WordPress. Local snippet files created or updated remotely are renamed on disk to the `<id>-<slug>` convention.';
|
|
17
17
|
static enableJsonFlag = true;
|
|
18
|
-
static examples = ['$ lps snippet push', '$ lps snippet push
|
|
18
|
+
static examples = ['$ lps snippet push', '$ lps snippet push ./snippets'];
|
|
19
19
|
static flags = {
|
|
20
20
|
...PushCommand.dryRunFlag,
|
|
21
21
|
...PushCommand.yesFlag,
|
package/dist/commands/status.js
CHANGED
|
@@ -37,7 +37,7 @@ export default class Status extends Command {
|
|
|
37
37
|
}
|
|
38
38
|
this.log(`Project: ${project.name} (${env.name})`);
|
|
39
39
|
this.log(`URL: ${env.url}`);
|
|
40
|
-
return { project: `${project.name} (${env.name})`, url: env.url };
|
|
40
|
+
return { environment: env.name, project: `${project.name} (${env.name})`, url: env.url };
|
|
41
41
|
}
|
|
42
42
|
// Mirrors base.ts:resolveEnvironment with --env: the targeted project comes from
|
|
43
43
|
// loopress.json when pinned, from the globally active project otherwise.
|
|
@@ -54,7 +54,7 @@ export default class Status extends Command {
|
|
|
54
54
|
}
|
|
55
55
|
this.log(`Project: ${project.name} (${env.name}, via --env)`);
|
|
56
56
|
this.log(`URL: ${env.url}`);
|
|
57
|
-
return { project: `${project.name} (${env.name}, via --env)`, url: env.url };
|
|
57
|
+
return { environment: env.name, project: `${project.name} (${env.name}, via --env)`, url: env.url };
|
|
58
58
|
}
|
|
59
59
|
reportPinnedProject(projectId) {
|
|
60
60
|
const project = configManager.getProject(projectId);
|
|
@@ -75,7 +75,7 @@ export default class Status extends Command {
|
|
|
75
75
|
const env = project.environments[envNames[0]];
|
|
76
76
|
this.log(`Project: ${project.name} (${env.name})`);
|
|
77
77
|
this.log(`URL: ${env.url}`);
|
|
78
|
-
return { project: `${project.name} (${env.name})`, url: env.url };
|
|
78
|
+
return { environment: env.name, project: `${project.name} (${env.name})`, url: env.url };
|
|
79
79
|
}
|
|
80
80
|
const current = configManager.getCurrentProject();
|
|
81
81
|
const currentEnv = current?.id === projectId ? configManager.getCurrentEnv() : null;
|
|
@@ -93,6 +93,6 @@ export default class Status extends Command {
|
|
|
93
93
|
}
|
|
94
94
|
this.log(`Project: ${project.name} (${currentEnv.name})`);
|
|
95
95
|
this.log(`URL: ${currentEnv.url}`);
|
|
96
|
-
return { project: `${project.name} (${currentEnv.name})`, url: currentEnv.url };
|
|
96
|
+
return { environment: currentEnv.name, project: `${project.name} (${currentEnv.name})`, url: currentEnv.url };
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
type PullTargetResult = {
|
|
3
|
+
error?: string;
|
|
4
|
+
label: string;
|
|
5
|
+
status: 'failed' | 'pulled';
|
|
6
|
+
};
|
|
2
7
|
type PullResult = {
|
|
3
|
-
|
|
4
|
-
merged: Record<string, string>;
|
|
5
|
-
status: 'composer-managed' | 'dry-run' | 'success';
|
|
6
|
-
updated: Array<{
|
|
7
|
-
from: string;
|
|
8
|
-
slug: string;
|
|
9
|
-
to: string;
|
|
10
|
-
}>;
|
|
8
|
+
results: PullTargetResult[];
|
|
11
9
|
};
|
|
12
10
|
export default class Pull extends LoopressCommand {
|
|
13
11
|
static description: string;
|
|
@@ -1,37 +1,41 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
1
|
import { LoopressCommand } from '../../lib/base.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
2
|
+
import { stdoutToStderr } from '../../lib/json-delegation.js';
|
|
3
|
+
import { pluralize } from '../../utils/pluralize.js';
|
|
4
|
+
// Templates and parts have no pull: the child theme is written from the project, never read back.
|
|
5
|
+
const PULL_TARGETS = [
|
|
6
|
+
{ commandId: 'theme:version:pull', label: 'theme versions' },
|
|
7
|
+
{ commandId: 'theme:style:pull', label: 'Global Styles' },
|
|
8
|
+
];
|
|
7
9
|
export default class Pull extends LoopressCommand {
|
|
8
|
-
static description = 'Pull
|
|
10
|
+
static description = 'Pull everything theme related from WordPress: theme versions and Global Styles';
|
|
9
11
|
static enableJsonFlag = true;
|
|
10
|
-
static examples = ['$ lps theme pull'];
|
|
12
|
+
static examples = ['$ lps theme pull', '$ lps theme pull --env staging', '$ lps theme pull --dry-run'];
|
|
11
13
|
static flags = {
|
|
12
14
|
...LoopressCommand.dryRunFlag,
|
|
13
15
|
};
|
|
14
16
|
async run() {
|
|
15
|
-
const
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const argv = ['--env', this.siteConfig.name];
|
|
18
|
+
if (this.dryRun)
|
|
19
|
+
argv.push('--dry-run');
|
|
20
|
+
const results = [];
|
|
21
|
+
for (const target of PULL_TARGETS) {
|
|
22
|
+
this.log(`\n→ Pulling ${target.label}...`);
|
|
23
|
+
try {
|
|
24
|
+
await stdoutToStderr(this.jsonEnabled(), async () => this.config.runCommand(target.commandId, argv));
|
|
25
|
+
this.log(`✓ ${target.label} pulled`);
|
|
26
|
+
results.push({ label: target.label, status: 'pulled' });
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
const { message } = error;
|
|
30
|
+
this.log(`✗ ${target.label} failed: ${message}`);
|
|
31
|
+
results.push({ error: message, label: target.label, status: 'failed' });
|
|
32
|
+
}
|
|
19
33
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const { added, merged, updated } = mergePluginManifest(this.localConfig.themes ?? {}, incoming);
|
|
25
|
-
if (this.dryRun) {
|
|
26
|
-
this.log(`[dry-run] Would write ${Object.keys(merged).length} themes to loopress.json`);
|
|
27
|
-
return { added, merged, status: 'dry-run', updated };
|
|
34
|
+
const failures = results.filter((result) => result.status === 'failed');
|
|
35
|
+
if (failures.length > 0) {
|
|
36
|
+
const reasons = failures.map((result) => `${result.label}: ${result.error}`);
|
|
37
|
+
this.error(`${pluralize(failures.length, 'theme resource')} failed to pull. ${reasons.join('; ')}`);
|
|
28
38
|
}
|
|
29
|
-
|
|
30
|
-
this.log(`Wrote ${Object.keys(merged).length} themes to loopress.json`);
|
|
31
|
-
if (added.length > 0)
|
|
32
|
-
this.log(` + Added: ${added.join(', ')}`);
|
|
33
|
-
for (const u of updated)
|
|
34
|
-
this.log(` ~ Updated: ${u.slug} ${u.from} → ${u.to}`);
|
|
35
|
-
return { added, merged, status: 'success', updated };
|
|
39
|
+
return { results };
|
|
36
40
|
}
|
|
37
41
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
type PushTargetResult = {
|
|
3
|
+
error?: string;
|
|
4
|
+
label: string;
|
|
5
|
+
status: 'failed' | 'pushed';
|
|
6
|
+
};
|
|
2
7
|
type PushResult = {
|
|
3
|
-
|
|
4
|
-
pinned: string[];
|
|
5
|
-
removed: string[];
|
|
6
|
-
status: 'composer-managed' | 'dry-run' | 'in-sync' | 'success';
|
|
8
|
+
results: PushTargetResult[];
|
|
7
9
|
};
|
|
8
|
-
export default class Push extends
|
|
10
|
+
export default class Push extends LoopressCommand {
|
|
9
11
|
static description: string;
|
|
10
12
|
static enableJsonFlag: boolean;
|
|
11
13
|
static examples: string[];
|
|
@@ -15,8 +17,5 @@ export default class Push extends PushCommand {
|
|
|
15
17
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
18
|
};
|
|
17
19
|
run(): Promise<PushResult>;
|
|
18
|
-
private fetchInstanceLock;
|
|
19
|
-
private guardForce;
|
|
20
|
-
private sync;
|
|
21
20
|
}
|
|
22
21
|
export {};
|