@loopress/cli 0.28.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 +370 -203
- 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/push.js +1 -1
- package/dist/commands/composer/push.js +3 -3
- 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 -38
- 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/pull.d.ts +4 -0
- package/dist/commands/plugin/pull.js +56 -10
- package/dist/commands/plugin/push.js +5 -4
- package/dist/commands/push.js +5 -4
- 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 -10
- package/dist/commands/theme/pull.js +30 -32
- package/dist/commands/theme/push.d.ts +8 -9
- package/dist/commands/theme/push.js +54 -111
- package/dist/commands/theme/style/pull.d.ts +7 -1
- package/dist/commands/theme/style/pull.js +3 -1
- package/dist/commands/theme/style/push.d.ts +8 -1
- package/dist/commands/theme/style/push.js +4 -1
- package/dist/commands/{template → theme/template}/diff.d.ts +1 -1
- package/dist/commands/{template → theme/template}/diff.js +3 -3
- package/dist/commands/{template → theme/template}/list.d.ts +2 -2
- package/dist/commands/{template → theme/template}/list.js +5 -5
- package/dist/commands/{template → theme/template}/push.d.ts +1 -1
- package/dist/commands/{template → theme/template}/push.js +8 -6
- 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/app-manifest.js +2 -2
- package/dist/lib/base.d.ts +1 -0
- package/dist/lib/base.js +3 -0
- package/dist/lib/dev-watch.js +2 -2
- 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 +7 -6
- package/dist/lib/snapshot-store.js +2 -2
- package/dist/lib/wp-authorize-flow.d.ts +10 -17
- package/dist/lib/wp-authorize-flow.js +24 -28
- package/dist/lib/wp-site-diagnostic.js +37 -0
- package/dist/utils/composer.d.ts +3 -1
- package/dist/utils/composer.js +14 -6
- package/dist/utils/option-format.d.ts +3 -1
- package/dist/utils/option-format.js +14 -3
- package/dist/utils/page-format.d.ts +5 -1
- package/dist/utils/page-format.js +7 -3
- package/dist/utils/plugin-sync.js +1 -1
- package/dist/utils/resource-dirs.js +2 -2
- package/dist/utils/template-format.d.ts +2 -1
- package/dist/utils/template-format.js +10 -3
- package/oclif.manifest.json +937 -361
- package/package.json +22 -16
package/dist/commands/push.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join } 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 { isLocalEnvironment } from '../lib/wp-client.js';
|
|
7
8
|
import { wpackagistRequire } from '../utils/composer.js';
|
|
8
9
|
import { pluralize } from '../utils/pluralize.js';
|
|
9
10
|
// Dependency-ish order: code first (plugins, composer), then the ACF definitions content
|
|
@@ -16,7 +17,7 @@ const PUSH_TARGETS = [
|
|
|
16
17
|
{ commandId: 'hook:push', label: 'hooks' },
|
|
17
18
|
{ commandId: 'form:push', label: 'forms' },
|
|
18
19
|
// Before pages: WordPress refuses a page whose `template` it doesn't know yet.
|
|
19
|
-
{ commandId: 'template:push', label: 'templates and parts' },
|
|
20
|
+
{ commandId: 'theme:template:push', label: 'templates and parts' },
|
|
20
21
|
{ commandId: 'page:push', label: 'pages' },
|
|
21
22
|
{ commandId: 'seo:push', label: 'SEO' },
|
|
22
23
|
{ commandId: 'menu:push', label: 'menus' },
|
|
@@ -46,7 +47,7 @@ export default class Push extends LoopressCommand {
|
|
|
46
47
|
this.log(`\n→ Pushing ${target.label}...`);
|
|
47
48
|
try {
|
|
48
49
|
await stdoutToStderr(this.jsonEnabled(), async () => this.config.runCommand(target.commandId, argv));
|
|
49
|
-
this.log(`✓ ${target.label} pushed`);
|
|
50
|
+
this.log(this.dryRun ? `✓ ${target.label} would push` : `✓ ${target.label} pushed`);
|
|
50
51
|
results.push({ label: target.label, status: 'pushed' });
|
|
51
52
|
}
|
|
52
53
|
catch (error) {
|
|
@@ -64,7 +65,7 @@ export default class Push extends LoopressCommand {
|
|
|
64
65
|
.map((result) => `${result.label}: ${result.error}`);
|
|
65
66
|
this.error(`${pluralize(this.failedCount, 'resource')} failed to push. ${failures.join('; ')}`);
|
|
66
67
|
}
|
|
67
|
-
this.log('\nAll resources pushed.');
|
|
68
|
+
this.log(this.dryRun ? '\nEvery resource would push.' : '\nAll resources pushed.');
|
|
68
69
|
return { results };
|
|
69
70
|
}
|
|
70
71
|
// Every delegated push always gets --yes: either this command's own guard already confirmed
|
|
@@ -84,7 +85,7 @@ export default class Push extends LoopressCommand {
|
|
|
84
85
|
return false;
|
|
85
86
|
try {
|
|
86
87
|
const composerJson = JSON.parse(await readFile(path, 'utf8'));
|
|
87
|
-
return Object.keys(wpackagistRequire(composerJson, 'plugin')).length > 0;
|
|
88
|
+
return Object.keys(wpackagistRequire(composerJson, 'plugin', isLocalEnvironment(this.siteConfig.name))).length > 0;
|
|
88
89
|
}
|
|
89
90
|
catch {
|
|
90
91
|
// Unreadable: let both commands run and report the parse error themselves.
|
|
@@ -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,15 +1,11 @@
|
|
|
1
1
|
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
-
|
|
2
|
+
type PullTargetResult = {
|
|
3
|
+
error?: string;
|
|
4
|
+
label: string;
|
|
5
|
+
status: 'failed' | 'pulled';
|
|
6
|
+
};
|
|
3
7
|
type PullResult = {
|
|
4
|
-
|
|
5
|
-
merged: Record<string, string>;
|
|
6
|
-
skipped?: ComposerPullResult['skipped'];
|
|
7
|
-
status: 'dry-run' | 'success';
|
|
8
|
-
updated: Array<{
|
|
9
|
-
from: string;
|
|
10
|
-
slug: string;
|
|
11
|
-
to: string;
|
|
12
|
-
}>;
|
|
8
|
+
results: PullTargetResult[];
|
|
13
9
|
};
|
|
14
10
|
export default class Pull extends LoopressCommand {
|
|
15
11
|
static description: string;
|
|
@@ -1,43 +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
|
-
|
|
7
|
-
|
|
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
|
+
];
|
|
8
9
|
export default class Pull extends LoopressCommand {
|
|
9
|
-
static description = 'Pull
|
|
10
|
+
static description = 'Pull everything theme related from WordPress: theme versions and Global Styles';
|
|
10
11
|
static enableJsonFlag = true;
|
|
11
|
-
static examples = ['$ lps theme pull'];
|
|
12
|
+
static examples = ['$ lps theme pull', '$ lps theme pull --env staging', '$ lps theme pull --dry-run'];
|
|
12
13
|
static flags = {
|
|
13
14
|
...LoopressCommand.dryRunFlag,
|
|
14
15
|
};
|
|
15
16
|
async run() {
|
|
16
|
-
const
|
|
17
|
-
this.
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
}
|
|
29
33
|
}
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
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('; ')}`);
|
|
34
38
|
}
|
|
35
|
-
|
|
36
|
-
this.log(`Wrote ${Object.keys(merged).length} themes to loopress.json`);
|
|
37
|
-
if (added.length > 0)
|
|
38
|
-
this.log(` + Added: ${added.join(', ')}`);
|
|
39
|
-
for (const u of updated)
|
|
40
|
-
this.log(` ~ Updated: ${u.slug} ${u.from} → ${u.to}`);
|
|
41
|
-
return { added, merged, status: 'success', updated };
|
|
39
|
+
return { results };
|
|
42
40
|
}
|
|
43
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 {};
|
|
@@ -1,123 +1,66 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
import { LoopressCommand } from '../../lib/base.js';
|
|
5
|
+
import { guardProductionPush } from '../../lib/guard-production-push.js';
|
|
6
|
+
import { stdoutToStderr } from '../../lib/json-delegation.js';
|
|
7
|
+
import { pluralize } from '../../utils/pluralize.js';
|
|
8
|
+
// The parent theme must be installed before its child theme is written, and Global Styles
|
|
9
|
+
// belong to whichever theme is active, so they go last.
|
|
10
|
+
const PUSH_TARGETS = [
|
|
11
|
+
{ commandId: 'theme:version:push', label: 'theme versions' },
|
|
12
|
+
{ commandId: 'theme:template:push', label: 'templates and parts' },
|
|
13
|
+
{ commandId: 'theme:style:push', label: 'Global Styles' },
|
|
14
|
+
];
|
|
15
|
+
export default class Push extends LoopressCommand {
|
|
16
|
+
static description = 'Push everything theme related to WordPress: theme versions, templates and parts, then Global Styles';
|
|
13
17
|
static enableJsonFlag = true;
|
|
14
|
-
static examples = ['$ lps theme push', '$ lps theme push --
|
|
18
|
+
static examples = ['$ lps theme push', '$ lps theme push --env staging', '$ lps theme push --dry-run'];
|
|
15
19
|
static flags = {
|
|
16
|
-
...
|
|
17
|
-
...
|
|
18
|
-
force: Flags.boolean({ default: false, description: '
|
|
20
|
+
...LoopressCommand.dryRunFlag,
|
|
21
|
+
...LoopressCommand.yesFlag,
|
|
22
|
+
force: Flags.boolean({ default: false, description: 'Passed to `lps theme version push`' }),
|
|
19
23
|
};
|
|
20
24
|
async run() {
|
|
21
25
|
const { flags } = await this.parse(Push);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return { ...IN_SYNC };
|
|
54
|
-
}
|
|
55
|
-
if (isNoop(diff))
|
|
56
|
-
this.log('Refreshing themes pinned to "latest" to their newest releases.');
|
|
57
|
-
for (const a of diff.toInstall)
|
|
58
|
-
this.log(` + ${a.slug} ${a.version}`);
|
|
59
|
-
for (const p of diff.toPin)
|
|
60
|
-
this.log(` ~ ${p.slug} ${p.from} to ${p.to}`);
|
|
61
|
-
for (const s of diff.toRemove)
|
|
62
|
-
this.log(` - ${s}`);
|
|
63
|
-
for (const c of diff.collisions)
|
|
64
|
-
this.log(` ! ${c.slug} (take over)`);
|
|
65
|
-
if (this.dryRun)
|
|
66
|
-
return result(diff, 'dry-run', diff.toRemove, true);
|
|
67
|
-
if (!(await confirmUninstall(diff.toRemove, this.yes)))
|
|
68
|
-
this.error('Aborted.');
|
|
69
|
-
const response = await this.sync(manifest, force);
|
|
70
|
-
if (response.output.trim())
|
|
71
|
-
this.log(response.output.trim());
|
|
72
|
-
this.log('Themes synced.');
|
|
73
|
-
await this.recordSuccess();
|
|
74
|
-
return result(diff, 'success', response.removed ?? diff.toRemove, force);
|
|
75
|
-
}
|
|
76
|
-
async fetchInstanceLock() {
|
|
77
|
-
try {
|
|
78
|
-
const { composerLock } = await this.wp.get('loopress/v1/composer/lock');
|
|
79
|
-
return composerLock;
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
if (isNotFoundError(error))
|
|
83
|
-
return null;
|
|
84
|
-
throw error;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
guardForce(diff, force) {
|
|
88
|
-
if (force)
|
|
89
|
-
return;
|
|
90
|
-
if (diff.collisions.length > 0) {
|
|
91
|
-
const list = diff.collisions.map((c) => `${c.slug} (${c.installedVersion})`).join(', ');
|
|
92
|
-
this.error(`${list} installed outside Loopress. Re-run with --force to take them over, or remove them from loopress.json.`);
|
|
93
|
-
}
|
|
94
|
-
const downgrades = diff.toPin.filter((p) => isDowngrade(p.from, p.to));
|
|
95
|
-
if (downgrades.length > 0) {
|
|
96
|
-
const list = downgrades.map((p) => `${p.slug} ${p.from} to ${p.to}`).join(', ');
|
|
97
|
-
this.error(`Refusing to downgrade: ${list}. Re-run with --force.`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
async sync(manifest, force) {
|
|
101
|
-
try {
|
|
102
|
-
return await this.wp.post('loopress/v1/composer/sync', { force, intent: { themes: manifest }, lock: null }, { timeoutMs: SYNC_TIMEOUT_MS });
|
|
103
|
-
}
|
|
104
|
-
catch (error) {
|
|
105
|
-
if (parseCollisions(error)) {
|
|
106
|
-
this.error('The site rejected the push: themes installed outside Loopress. Re-run with --force.');
|
|
26
|
+
// Guarded once here, like `lps push`. Steps get --production-confirmed rather than --yes,
|
|
27
|
+
// so the versions step still asks before uninstalling themes unless the user passed --yes.
|
|
28
|
+
await guardProductionPush({
|
|
29
|
+
dryRun: this.dryRun,
|
|
30
|
+
error: (message) => this.error(message),
|
|
31
|
+
siteConfig: this.siteConfig,
|
|
32
|
+
yes: this.yes,
|
|
33
|
+
});
|
|
34
|
+
// Nothing to install and no composer.json: the versions step would only fail on an empty
|
|
35
|
+
// manifest, which must not fail a templates or styles only project.
|
|
36
|
+
const hasVersions = Object.keys(this.localConfig.themes ?? {}).length > 0 || existsSync(join(process.cwd(), this.rootDir, 'composer.json'));
|
|
37
|
+
const targets = hasVersions ? PUSH_TARGETS : PUSH_TARGETS.filter((target) => target.commandId !== 'theme:version:push');
|
|
38
|
+
const results = [];
|
|
39
|
+
for (const target of targets) {
|
|
40
|
+
const argv = ['--env', this.siteConfig.name, '--production-confirmed'];
|
|
41
|
+
if (this.yes)
|
|
42
|
+
argv.push('--yes');
|
|
43
|
+
if (this.dryRun)
|
|
44
|
+
argv.push('--dry-run');
|
|
45
|
+
if (flags.force && target.commandId === 'theme:version:push')
|
|
46
|
+
argv.push('--force');
|
|
47
|
+
this.log(`\n→ Pushing ${target.label}...`);
|
|
48
|
+
try {
|
|
49
|
+
await stdoutToStderr(this.jsonEnabled(), async () => this.config.runCommand(target.commandId, argv));
|
|
50
|
+
this.log(`✓ ${target.label} pushed`);
|
|
51
|
+
results.push({ label: target.label, status: 'pushed' });
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const { message } = error;
|
|
55
|
+
this.log(`✗ ${target.label} failed: ${message}`);
|
|
56
|
+
results.push({ error: message, label: target.label, status: 'failed' });
|
|
107
57
|
}
|
|
108
|
-
throw error;
|
|
109
58
|
}
|
|
59
|
+
const failures = results.filter((result) => result.status === 'failed');
|
|
60
|
+
if (failures.length > 0) {
|
|
61
|
+
const reasons = failures.map((result) => `${result.label}: ${result.error}`);
|
|
62
|
+
this.error(`${pluralize(failures.length, 'theme resource')} failed to push. ${reasons.join('; ')}`);
|
|
63
|
+
}
|
|
64
|
+
return { results };
|
|
110
65
|
}
|
|
111
66
|
}
|
|
112
|
-
function isNoop(diff) {
|
|
113
|
-
return (diff.toInstall.length === 0 && diff.toPin.length === 0 && diff.toRemove.length === 0 && diff.collisions.length === 0);
|
|
114
|
-
}
|
|
115
|
-
function result(diff, status, removed, includeCollisions) {
|
|
116
|
-
const collisionSlugs = includeCollisions ? diff.collisions.map((c) => c.slug) : [];
|
|
117
|
-
return {
|
|
118
|
-
installed: [...diff.toInstall.map((a) => a.slug), ...collisionSlugs],
|
|
119
|
-
pinned: diff.toPin.map((p) => p.slug),
|
|
120
|
-
removed,
|
|
121
|
-
status,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { LoopressCommand } from '../../../lib/base.js';
|
|
2
|
+
type ThemeStylePullResult = {
|
|
3
|
+
file: string;
|
|
4
|
+
status: 'dry-run' | 'success';
|
|
5
|
+
};
|
|
2
6
|
export default class Pull extends LoopressCommand {
|
|
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
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
15
|
};
|
|
11
|
-
run(): Promise<
|
|
16
|
+
run(): Promise<ThemeStylePullResult>;
|
|
12
17
|
}
|
|
18
|
+
export {};
|
|
@@ -8,6 +8,7 @@ export default class Pull extends LoopressCommand {
|
|
|
8
8
|
path: Args.string({ description: 'Path to theme styles directory (overrides project config)' }),
|
|
9
9
|
};
|
|
10
10
|
static description = "Pull the active block theme's Global Styles customizations (Site Editor > Styles) from WordPress into a local file";
|
|
11
|
+
static enableJsonFlag = true;
|
|
11
12
|
static examples = ['$ lps theme style pull'];
|
|
12
13
|
static flags = {
|
|
13
14
|
...LoopressCommand.dryRunFlag,
|
|
@@ -22,10 +23,11 @@ export default class Pull extends LoopressCommand {
|
|
|
22
23
|
const file = join(dir, themeStylesFileName(stylesheet));
|
|
23
24
|
if (this.dryRun) {
|
|
24
25
|
this.log(`[dry-run] Would pull Global Styles for "${stylesheet}" to ${file}`);
|
|
25
|
-
return;
|
|
26
|
+
return { file, status: 'dry-run' };
|
|
26
27
|
}
|
|
27
28
|
await mkdir(dir, { recursive: true });
|
|
28
29
|
await writeFile(file, JSON.stringify(canonicalGlobalStyles(item), null, 2) + '\n');
|
|
29
30
|
this.log(`Pulled Global Styles for "${stylesheet}" to ${file}`);
|
|
31
|
+
return { file, status: 'success' };
|
|
30
32
|
}
|
|
31
33
|
}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { PushCommand } from '../../../lib/push-command.js';
|
|
2
|
+
type ThemeStylePushResult = {
|
|
3
|
+
file: 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: {
|
|
14
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
15
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
16
|
};
|
|
11
|
-
run(): Promise<
|
|
17
|
+
run(): Promise<ThemeStylePushResult>;
|
|
12
18
|
}
|
|
19
|
+
export {};
|
|
@@ -9,9 +9,11 @@ export default class Push extends PushCommand {
|
|
|
9
9
|
path: Args.string({ description: 'Path to theme styles directory (overrides project config)' }),
|
|
10
10
|
};
|
|
11
11
|
static description = "Push the local Global Styles file to the active block theme's Site Editor > Styles on WordPress";
|
|
12
|
+
static enableJsonFlag = true;
|
|
12
13
|
static examples = ['$ lps theme style push'];
|
|
13
14
|
static flags = {
|
|
14
15
|
...PushCommand.dryRunFlag,
|
|
16
|
+
...PushCommand.yesFlag,
|
|
15
17
|
};
|
|
16
18
|
async run() {
|
|
17
19
|
const { args } = await this.parse(Push);
|
|
@@ -32,7 +34,7 @@ export default class Push extends PushCommand {
|
|
|
32
34
|
}
|
|
33
35
|
if (this.dryRun) {
|
|
34
36
|
this.log(`[dry-run] Would push: ${file}`);
|
|
35
|
-
return;
|
|
37
|
+
return { file, status: 'dry-run' };
|
|
36
38
|
}
|
|
37
39
|
const provider = getResourceStateProvider('theme-styles');
|
|
38
40
|
const beforeState = await this.captureBeforePushState(provider, dir);
|
|
@@ -41,5 +43,6 @@ export default class Push extends PushCommand {
|
|
|
41
43
|
this.log(`Pushed: ${file}`);
|
|
42
44
|
await this.writeAfterPushSnapshot(provider, dir, beforeState);
|
|
43
45
|
await this.recordSuccess();
|
|
46
|
+
return { file, status: 'success' };
|
|
44
47
|
}
|
|
45
48
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { DiffCommand } from '
|
|
2
|
-
import { getResourceStateProvider } from '
|
|
1
|
+
import { DiffCommand } from '../../../lib/diff-command.js';
|
|
2
|
+
import { getResourceStateProvider } from '../../../lib/resource-state.js';
|
|
3
3
|
export default class Diff extends DiffCommand {
|
|
4
4
|
static description = 'Show what differs in block templates and parts between your local files and a WordPress environment, or between two environments. Templates and parts edited in the Site Editor show up too.';
|
|
5
5
|
static enableJsonFlag = true;
|
|
6
|
-
static examples = ['$ lps template diff', '$ lps template diff --env staging', '$ lps template diff --env staging --against production'];
|
|
6
|
+
static examples = ['$ lps theme template diff', '$ lps theme template diff --env staging', '$ lps theme template diff --env staging --against production'];
|
|
7
7
|
static flags = { ...DiffCommand.againstFlag };
|
|
8
8
|
async run() {
|
|
9
9
|
const { flags } = await this.parse(Diff);
|