@loopress/cli 0.32.2 → 0.34.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 +549 -179
- package/dist/commands/acf/list.d.ts +1 -1
- package/dist/commands/acf/list.js +3 -4
- package/dist/commands/acf/pull.d.ts +1 -1
- package/dist/commands/acf/pull.js +9 -6
- package/dist/commands/acf/push.d.ts +1 -1
- package/dist/commands/acf/push.js +5 -4
- package/dist/commands/api/publish.js +2 -1
- package/dist/commands/app/pull.js +2 -1
- package/dist/commands/app/push.js +7 -5
- package/dist/commands/app/{remove.d.ts → rm.d.ts} +1 -0
- package/dist/commands/app/{remove.js → rm.js} +2 -1
- package/dist/commands/block/diff.d.ts +2 -0
- package/dist/commands/block/diff.js +5 -0
- package/dist/commands/block/list.d.ts +8 -0
- package/dist/commands/block/list.js +23 -0
- package/dist/commands/block/pull.d.ts +21 -0
- package/dist/commands/block/pull.js +67 -0
- package/dist/commands/block/push.d.ts +20 -0
- package/dist/commands/block/push.js +53 -0
- package/dist/commands/block/rm.d.ts +23 -0
- package/dist/commands/block/rm.js +69 -0
- package/dist/commands/block/rollback.d.ts +2 -0
- package/dist/commands/block/rollback.js +5 -0
- package/dist/commands/composer/init.js +1 -0
- package/dist/commands/composer/push.js +5 -4
- package/dist/commands/content/copy.d.ts +3 -0
- package/dist/commands/content/copy.js +79 -2
- package/dist/commands/dev.d.ts +2 -2
- package/dist/commands/dev.js +13 -18
- package/dist/commands/diff.d.ts +6 -3
- package/dist/commands/diff.js +63 -13
- package/dist/commands/doctor.d.ts +4 -2
- package/dist/commands/doctor.js +49 -25
- package/dist/commands/form/pull.js +7 -5
- package/dist/commands/form/push.js +4 -3
- package/dist/commands/init.js +4 -3
- package/dist/commands/menu/push.js +8 -8
- package/dist/commands/option/list.js +5 -2
- package/dist/commands/option/push.js +3 -1
- package/dist/commands/option/{remove.d.ts → rm.d.ts} +1 -0
- package/dist/commands/option/{remove.js → rm.js} +2 -1
- package/dist/commands/page/push.js +3 -1
- package/dist/commands/plugin/add.d.ts +1 -0
- package/dist/commands/plugin/add.js +50 -9
- package/dist/commands/plugin/audit.d.ts +4 -4
- package/dist/commands/plugin/audit.js +35 -15
- package/dist/commands/plugin/pull.js +3 -2
- package/dist/commands/plugin/push.js +41 -6
- package/dist/commands/plugin/status.js +3 -1
- package/dist/commands/project/config.js +7 -7
- package/dist/commands/project/pull.js +3 -1
- package/dist/commands/project/push.js +6 -4
- package/dist/commands/project/{remove.d.ts → rm.d.ts} +1 -0
- package/dist/commands/project/{remove.js → rm.js} +3 -2
- package/dist/commands/project/switch.js +6 -4
- package/dist/commands/promote.d.ts +1 -0
- package/dist/commands/promote.js +15 -6
- package/dist/commands/pull.d.ts +7 -0
- package/dist/commands/pull.js +67 -18
- package/dist/commands/push.d.ts +11 -1
- package/dist/commands/push.js +157 -48
- package/dist/commands/snippet/publish.js +9 -5
- package/dist/commands/snippet/push.d.ts +1 -1
- package/dist/commands/snippet/push.js +15 -12
- package/dist/commands/status.js +1 -0
- package/dist/commands/theme/activate.d.ts +21 -0
- package/dist/commands/theme/activate.js +44 -0
- package/dist/commands/theme/pull.js +2 -1
- package/dist/commands/theme/push.js +7 -3
- package/dist/commands/theme/style/diff.js +1 -1
- package/dist/commands/theme/style/push.js +8 -3
- package/dist/commands/theme/style/rollback.js +1 -1
- package/dist/commands/theme/template/push.d.ts +2 -0
- package/dist/commands/theme/template/push.js +66 -18
- package/dist/commands/validate.js +3 -0
- package/dist/config/auth.manager.d.ts +10 -7
- package/dist/config/auth.manager.js +5 -1
- package/dist/config/json-file.d.ts +1 -1
- package/dist/config/project-config.manager.d.ts +2 -2
- package/dist/config/project-config.manager.js +13 -21
- package/dist/lib/api-client.js +16 -21
- package/dist/lib/app-build.js +6 -2
- package/dist/lib/app-manifest.js +6 -6
- package/dist/lib/base.d.ts +8 -1
- package/dist/lib/base.js +43 -9
- package/dist/lib/bootstrap-full-install.js +8 -6
- package/dist/lib/capabilities.d.ts +44 -0
- package/dist/lib/capabilities.js +85 -0
- package/dist/lib/declared-plugins.d.ts +9 -0
- package/dist/lib/declared-plugins.js +21 -0
- package/dist/lib/declared-type-command.js +7 -2
- package/dist/lib/dev-watch.d.ts +1 -1
- package/dist/lib/dev-watch.js +13 -15
- package/dist/lib/diff-command.d.ts +19 -3
- package/dist/lib/diff-command.js +132 -14
- package/dist/lib/diff-state.js +2 -3
- package/dist/lib/environment-urls.d.ts +6 -0
- package/dist/lib/environment-urls.js +59 -0
- package/dist/lib/exclude.d.ts +16 -0
- package/dist/lib/exclude.js +102 -0
- package/dist/lib/load-files.js +2 -1
- package/dist/lib/load-snippets.js +39 -43
- package/dist/lib/local-callback-server.js +7 -1
- package/dist/lib/narrow.d.ts +5 -0
- package/dist/lib/narrow.js +26 -0
- package/dist/lib/php-files-command.js +13 -8
- package/dist/lib/plugin-detection.d.ts +0 -1
- package/dist/lib/plugin-detection.js +2 -4
- package/dist/lib/push-command.d.ts +5 -1
- package/dist/lib/push-command.js +48 -7
- package/dist/lib/put-or-create.d.ts +2 -2
- package/dist/lib/readdir-tolerant.js +9 -4
- package/dist/lib/resource-selection.d.ts +9 -0
- package/dist/lib/resource-selection.js +49 -0
- package/dist/lib/resource-state.d.ts +2 -0
- package/dist/lib/resource-state.js +60 -14
- package/dist/lib/rollback-command.d.ts +1 -1
- package/dist/lib/rollback-command.js +4 -3
- package/dist/lib/rollback-materialize.js +29 -9
- package/dist/lib/rotate-app-password.js +1 -0
- package/dist/lib/sentry.js +2 -2
- package/dist/lib/snapshot-store.d.ts +10 -7
- package/dist/lib/snapshot-store.js +21 -5
- package/dist/lib/spinner.js +4 -0
- package/dist/lib/validate-local.d.ts +1 -0
- package/dist/lib/validate-local.js +102 -28
- package/dist/lib/wp-client.d.ts +7 -1
- package/dist/lib/wp-client.js +60 -69
- package/dist/lib/wp-site-diagnostic.js +3 -5
- package/dist/types/global-config.generated.d.ts +1 -1
- package/dist/types/plugin.d.ts +7 -5
- package/dist/types/plugin.js +6 -1
- package/dist/types/project-config.generated.d.ts +24 -5
- package/dist/types/snippet.d.ts +2 -2
- package/dist/types/snippet.js +1 -1
- package/dist/utils/acf-format.js +6 -3
- package/dist/utils/block-format.d.ts +29 -0
- package/dist/utils/block-format.js +109 -0
- package/dist/utils/composer.d.ts +7 -15
- package/dist/utils/composer.js +43 -16
- package/dist/utils/declared-type-format.js +26 -19
- package/dist/utils/form-format.js +2 -2
- package/dist/utils/loopress-config.js +9 -1
- package/dist/utils/menu-format.d.ts +3 -1
- package/dist/utils/menu-format.js +5 -0
- package/dist/utils/option-format.d.ts +2 -2
- package/dist/utils/option-format.js +15 -25
- package/dist/utils/page-format.js +9 -7
- package/dist/utils/php-top-level.js +15 -4
- package/dist/utils/plugin-sync.d.ts +7 -7
- package/dist/utils/plugin-sync.js +11 -11
- package/dist/utils/plugins.js +9 -6
- package/dist/utils/resource-dirs.d.ts +2 -0
- package/dist/utils/resource-dirs.js +13 -5
- package/dist/utils/snippet-format.d.ts +1 -1
- package/dist/utils/snippet-format.js +5 -3
- package/dist/utils/template-format.js +2 -2
- package/dist/utils/theme-css.js +14 -6
- package/dist/utils/theme-styles-format.d.ts +3 -0
- package/dist/utils/theme-styles-format.js +15 -5
- package/dist/utils/version.js +1 -1
- package/oclif.manifest.json +2661 -1235
- package/package.json +45 -20
- package/schemas/global-config.schema.json +1 -1
- package/schemas/project-config.schema.json +32 -5
package/dist/commands/promote.js
CHANGED
|
@@ -8,7 +8,9 @@ import { configManager } from '../config/project-config.manager.js';
|
|
|
8
8
|
import { LoopressCommand } from '../lib/base.js';
|
|
9
9
|
import { isInteractive } from '../lib/interactive.js';
|
|
10
10
|
import { stdoutToStderr } from '../lib/json-delegation.js';
|
|
11
|
+
import { errorMessage } from '../lib/narrow.js';
|
|
11
12
|
import { PushCommand } from '../lib/push-command.js';
|
|
13
|
+
import {} from '../types/config.js';
|
|
12
14
|
import { readLocalConfig } from '../utils/loopress-config.js';
|
|
13
15
|
import { resolveResourceDir, RESOURCE_DIR_DEFAULTS } from '../utils/resource-dirs.js';
|
|
14
16
|
export default class Promote extends Command {
|
|
@@ -18,15 +20,17 @@ export default class Promote extends Command {
|
|
|
18
20
|
};
|
|
19
21
|
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.';
|
|
20
22
|
static enableJsonFlag = true;
|
|
21
|
-
static examples = ['$ lps promote staging production', '$ lps promote production staging --dry-run'];
|
|
23
|
+
static examples = ['$ lps promote staging production', '$ lps promote production staging --dry-run', '$ lps promote staging production --skip-unsupported'];
|
|
22
24
|
static flags = {
|
|
23
25
|
...LoopressCommand.dryRunFlag,
|
|
24
26
|
...LoopressCommand.yesFlag,
|
|
27
|
+
...LoopressCommand.skipUnsupportedFlag,
|
|
25
28
|
overwrite: PushCommand.baseFlags.overwrite,
|
|
26
29
|
};
|
|
27
30
|
async run() {
|
|
28
31
|
const { args, flags } = await this.parse(Promote);
|
|
29
32
|
const { overwrite, yes } = flags;
|
|
33
|
+
const skipUnsupported = flags['skip-unsupported'];
|
|
30
34
|
const dryRun = flags['dry-run'];
|
|
31
35
|
const { environments, projectName } = await this.resolveProject();
|
|
32
36
|
const from = this.requireEnvironment(environments, projectName, args.from);
|
|
@@ -50,16 +54,16 @@ export default class Promote extends Command {
|
|
|
50
54
|
try {
|
|
51
55
|
this.log(`\n=== Pulling from ${from.name} (${from.url}) ===`);
|
|
52
56
|
try {
|
|
53
|
-
await stdoutToStderr(this.jsonEnabled(), async () => this.config.runCommand('pull', this.delegateArgv(from.name, false)));
|
|
57
|
+
await stdoutToStderr(this.jsonEnabled(), async () => this.config.runCommand('pull', this.delegateArgv(from.name, false, skipUnsupported)));
|
|
54
58
|
}
|
|
55
59
|
catch (error) {
|
|
56
60
|
// A partial pull must never be pushed onward: stop before touching <to>.
|
|
57
61
|
// "untouched" is only true of <to>: a real (non dry-run) pull has already rewritten the files.
|
|
58
62
|
const filesNote = dryRun ? '' : ` Your local files may already hold part of ${from.name}, review them before pushing.`;
|
|
59
|
-
this.error(`Pull from ${from.name} failed, nothing was pushed to ${to.name}.${filesNote} ${error
|
|
63
|
+
this.error(`Pull from ${from.name} failed, nothing was pushed to ${to.name}.${filesNote} ${errorMessage(error)}`);
|
|
60
64
|
}
|
|
61
65
|
this.log(`\n=== Pushing to ${to.name} (${to.url}) ===`);
|
|
62
|
-
await stdoutToStderr(this.jsonEnabled(), async () => this.config.runCommand('push', [...this.delegateArgv(to.name, dryRun), ...(overwrite ? ['--overwrite'] : [])]));
|
|
66
|
+
await stdoutToStderr(this.jsonEnabled(), async () => this.config.runCommand('push', [...this.delegateArgv(to.name, dryRun, skipUnsupported), ...(overwrite ? ['--overwrite'] : [])]));
|
|
63
67
|
}
|
|
64
68
|
finally {
|
|
65
69
|
if (scratch) {
|
|
@@ -78,7 +82,10 @@ export default class Promote extends Command {
|
|
|
78
82
|
// resource dir would land outside the copy, in the user's real files, during a dry run.
|
|
79
83
|
async assertDirsInsideProject(projectDir) {
|
|
80
84
|
const localConfig = await readLocalConfig();
|
|
81
|
-
|
|
85
|
+
// Object.keys() is typed string[] even on an `as const` object: these are its own keys.
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- see above
|
|
87
|
+
const kinds = Object.keys(RESOURCE_DIR_DEFAULTS);
|
|
88
|
+
const dirs = [localConfig.rootDir ?? '.', ...kinds.map((kind) => resolveResourceDir(kind, localConfig))];
|
|
82
89
|
const outside = dirs.find((dir) => {
|
|
83
90
|
const rel = relative(projectDir, resolve(projectDir, dir));
|
|
84
91
|
return rel.startsWith('..') || isAbsolute(rel);
|
|
@@ -109,8 +116,10 @@ export default class Promote extends Command {
|
|
|
109
116
|
// `--yes` is always forwarded: confirmPromotion above already gathered intent once, so the
|
|
110
117
|
// delegated `pull` and `push` must not prompt again (push would otherwise re-run its own
|
|
111
118
|
// production guard, and pull its orphan-deletion prompt).
|
|
112
|
-
delegateArgv(env, dryRun) {
|
|
119
|
+
delegateArgv(env, dryRun, skipUnsupported) {
|
|
113
120
|
const argv = ['--env', env, '--yes'];
|
|
121
|
+
if (skipUnsupported)
|
|
122
|
+
argv.push('--skip-unsupported');
|
|
114
123
|
if (dryRun)
|
|
115
124
|
argv.push('--dry-run');
|
|
116
125
|
return argv;
|
package/dist/commands/pull.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { LoopressCommand } from '../lib/base.js';
|
|
|
2
2
|
type PullTargetResult = {
|
|
3
3
|
error?: string;
|
|
4
4
|
label: string;
|
|
5
|
+
reason?: string;
|
|
5
6
|
status: 'failed' | 'pulled' | 'skipped';
|
|
6
7
|
};
|
|
7
8
|
type PullResult = {
|
|
@@ -12,12 +13,18 @@ export default class Pull extends LoopressCommand {
|
|
|
12
13
|
static enableJsonFlag: boolean;
|
|
13
14
|
static examples: string[];
|
|
14
15
|
static flags: {
|
|
16
|
+
'skip-unsupported': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
17
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
18
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
19
|
};
|
|
18
20
|
private failedCount;
|
|
21
|
+
private skipUnsupported;
|
|
22
|
+
private readonly unsupported;
|
|
19
23
|
run(): Promise<PullResult>;
|
|
20
24
|
private buildArgv;
|
|
21
25
|
private globalStylesSkipNote;
|
|
26
|
+
private projectUses;
|
|
27
|
+
private unavailable;
|
|
28
|
+
private warnUnsupported;
|
|
22
29
|
}
|
|
23
30
|
export {};
|
package/dist/commands/pull.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { LoopressCommand } from '../lib/base.js';
|
|
2
|
+
import { integrationFix, missingIntegration, missingModule } from '../lib/capabilities.js';
|
|
3
|
+
import { declaredPlugins } from '../lib/declared-plugins.js';
|
|
2
4
|
import { stdoutToStderr } from '../lib/json-delegation.js';
|
|
3
|
-
import {
|
|
5
|
+
import { errorMessage } from '../lib/narrow.js';
|
|
4
6
|
import { pluralize } from '../utils/pluralize.js';
|
|
5
7
|
import { projectUses, resolveResourceDir } from '../utils/resource-dirs.js';
|
|
6
8
|
import { globalStylesSkipNote } from '../utils/theme-styles-format.js';
|
|
@@ -15,10 +17,11 @@ const PULL_TARGETS = [
|
|
|
15
17
|
{ commandId: 'meta:pull', label: 'post meta', supportsYes: true },
|
|
16
18
|
{ commandId: 'role:pull', label: 'roles', supportsYes: true },
|
|
17
19
|
{ commandId: 'acf:pull', label: 'ACF', needsPlugin: 'acf', supportsYes: true },
|
|
18
|
-
{ commandId: 'api:pull', label: 'API routes', supportsYes: true },
|
|
19
|
-
{ commandId: 'hook:pull', label: 'hooks', supportsYes: true },
|
|
20
|
+
{ commandId: 'api:pull', label: 'API routes', needsPlugin: 'api', supportsYes: true },
|
|
21
|
+
{ commandId: 'hook:pull', label: 'hooks', needsPlugin: 'hooks', supportsYes: true },
|
|
22
|
+
{ commandId: 'block:pull', label: 'dynamic blocks', needsPlugin: 'block', supportsYes: true },
|
|
20
23
|
{ commandId: 'form:pull', label: 'forms', needsPlugin: 'form', supportsYes: true },
|
|
21
|
-
{ commandId: 'pattern:pull', label: 'patterns', supportsYes: true },
|
|
24
|
+
{ commandId: 'pattern:pull', label: 'patterns', needsPlugin: 'pattern', supportsYes: true },
|
|
22
25
|
// Templates and parts have no pull. Global Styles are pulled only once the project tracks them,
|
|
23
26
|
// see globalStylesSkipNote.
|
|
24
27
|
{ commandId: 'theme:style:pull', label: 'Global Styles' },
|
|
@@ -26,18 +29,31 @@ const PULL_TARGETS = [
|
|
|
26
29
|
{ commandId: 'option:pull', label: 'options', supportsYes: true },
|
|
27
30
|
{ commandId: 'snippet:pull', label: 'snippets', needsPlugin: 'snippets', supportsYes: true },
|
|
28
31
|
];
|
|
32
|
+
// Why this site can't give the target. Composer has no directory to tell whether the project
|
|
33
|
+
// uses it: never pulled from Light, which has nothing to give.
|
|
34
|
+
function missingFor(target, capabilities) {
|
|
35
|
+
if (target.commandId === 'composer:pull')
|
|
36
|
+
return missingModule('dependencies', 'Composer files', capabilities);
|
|
37
|
+
return target.needsPlugin === undefined ? undefined : missingIntegration(target.needsPlugin, capabilities);
|
|
38
|
+
}
|
|
29
39
|
export default class Pull extends LoopressCommand {
|
|
30
|
-
static description = 'Pull every tracked resource from WordPress into local files in one run: composer dependencies, plugins, post types, taxonomies, post meta, roles, ACF, API routes, hooks, forms, patterns, Global Styles, menus, options, and snippets';
|
|
40
|
+
static description = 'Pull every tracked resource from WordPress into local files in one run: composer dependencies, plugins, post types, taxonomies, post meta, roles, ACF, API routes, hooks, dynamic blocks, forms, patterns, Global Styles, menus, options, and snippets';
|
|
31
41
|
static enableJsonFlag = true;
|
|
32
42
|
static examples = ['$ lps pull', '$ lps pull --env staging', '$ lps pull --dry-run'];
|
|
33
43
|
static flags = {
|
|
34
44
|
...LoopressCommand.dryRunFlag,
|
|
35
45
|
...LoopressCommand.yesFlag,
|
|
46
|
+
...LoopressCommand.skipUnsupportedFlag,
|
|
36
47
|
};
|
|
37
48
|
failedCount = 0;
|
|
49
|
+
skipUnsupported = false;
|
|
50
|
+
// What --skip-unsupported held back: the target and why, for the final warning.
|
|
51
|
+
unsupported = [];
|
|
38
52
|
async run() {
|
|
53
|
+
const { flags } = await this.parse(Pull);
|
|
54
|
+
this.skipUnsupported = flags['skip-unsupported'];
|
|
39
55
|
const results = [];
|
|
40
|
-
this.
|
|
56
|
+
const capabilities = await this.capabilities();
|
|
41
57
|
for (const target of PULL_TARGETS) {
|
|
42
58
|
const skipNote = target.commandId === 'theme:style:pull' ? await this.globalStylesSkipNote() : undefined;
|
|
43
59
|
if (skipNote) {
|
|
@@ -45,6 +61,11 @@ export default class Pull extends LoopressCommand {
|
|
|
45
61
|
results.push({ label: target.label, status: 'skipped' });
|
|
46
62
|
continue;
|
|
47
63
|
}
|
|
64
|
+
const unavailable = this.unavailable(target, capabilities);
|
|
65
|
+
if (unavailable) {
|
|
66
|
+
results.push(unavailable);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
48
69
|
this.log(`\n→ Pulling ${target.label}...`);
|
|
49
70
|
try {
|
|
50
71
|
await stdoutToStderr(this.jsonEnabled(), async () => this.config.runCommand(target.commandId, this.buildArgv(target)));
|
|
@@ -52,30 +73,21 @@ export default class Pull extends LoopressCommand {
|
|
|
52
73
|
results.push({ label: target.label, status: 'pulled' });
|
|
53
74
|
}
|
|
54
75
|
catch (error) {
|
|
55
|
-
const
|
|
56
|
-
// A site without ACF (or a form or snippet plugin) is the common case, not a failure,
|
|
57
|
-
// as long as the project doesn't use that resource: a vanilla site must pull, and a
|
|
58
|
-
// promote must not stop on it. A project that does use it still fails.
|
|
59
|
-
if (target.needsPlugin && INTEGRATION_MISSING.test(message) && !projectUses(target.needsPlugin, this.localConfig)) {
|
|
60
|
-
// oclif's catch already set exitCode to 1 for the sub-command; a real failure still
|
|
61
|
-
// exits non-zero through the this.error() below.
|
|
62
|
-
process.exitCode = undefined;
|
|
63
|
-
this.log(`→ ${target.label}: not configured, skipped`);
|
|
64
|
-
results.push({ label: target.label, status: 'skipped' });
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
76
|
+
const message = errorMessage(error);
|
|
67
77
|
this.failedCount++;
|
|
68
78
|
this.log(`✗ ${target.label} failed: ${message}`);
|
|
69
79
|
results.push({ error: message, label: target.label, status: 'failed' });
|
|
70
80
|
}
|
|
71
81
|
}
|
|
72
82
|
if (this.failedCount > 0) {
|
|
83
|
+
await this.warnUnsupported();
|
|
73
84
|
// Each failure's own reason, not just the count: under --json the per-resource lines above
|
|
74
85
|
// never reach stdout, and this message is all an MCP caller or a script gets to see.
|
|
75
86
|
const failures = results.filter((result) => result.status === 'failed').map((result) => `${result.label}: ${result.error}`);
|
|
76
87
|
this.error(`${pluralize(this.failedCount, 'resource')} failed to pull. ${failures.join('; ')}`);
|
|
77
88
|
}
|
|
78
89
|
this.log('\nAll resources pulled.');
|
|
90
|
+
await this.warnUnsupported();
|
|
79
91
|
return { results };
|
|
80
92
|
}
|
|
81
93
|
// `--yes` is forwarded only when the user passed it and the target accepts it: unlike
|
|
@@ -97,4 +109,41 @@ export default class Pull extends LoopressCommand {
|
|
|
97
109
|
return undefined;
|
|
98
110
|
}
|
|
99
111
|
}
|
|
112
|
+
// Composer has no directory: counted as unused, see missingFor.
|
|
113
|
+
projectUses(target) {
|
|
114
|
+
return target.needsPlugin !== undefined && projectUses(target.needsPlugin, this.localConfig);
|
|
115
|
+
}
|
|
116
|
+
// A site without ACF (or a form or snippet plugin) is the common case, not a failure, as
|
|
117
|
+
// long as the project doesn't use that resource: a vanilla site must pull, and a promote
|
|
118
|
+
// must not stop on it. A project that does use it still fails, without asking the site.
|
|
119
|
+
// Same for a Loopress Full feature on Light.
|
|
120
|
+
unavailable(target, capabilities) {
|
|
121
|
+
const missing = missingFor(target, capabilities);
|
|
122
|
+
if (!missing)
|
|
123
|
+
return undefined;
|
|
124
|
+
if (!this.projectUses(target)) {
|
|
125
|
+
this.log(`\n→ ${target.label}: ${missing.needsFull ? missing.reason : 'not configured'}, skipped`);
|
|
126
|
+
return { label: target.label, status: 'skipped' };
|
|
127
|
+
}
|
|
128
|
+
if (this.skipUnsupported) {
|
|
129
|
+
this.unsupported.push({ label: target.label, missing });
|
|
130
|
+
this.log(`\n→ ${target.label}: ${missing.reason}, skipped (--skip-unsupported)`);
|
|
131
|
+
return { label: target.label, reason: `${missing.reason} on ${this.siteConfig.name}`, status: 'skipped' };
|
|
132
|
+
}
|
|
133
|
+
this.failedCount++;
|
|
134
|
+
const error = `${missing.reason} on ${this.siteConfig.name}`;
|
|
135
|
+
this.log(`\n✗ ${target.label} failed: ${error}`);
|
|
136
|
+
return { error, label: target.label, status: 'failed' };
|
|
137
|
+
}
|
|
138
|
+
async warnUnsupported() {
|
|
139
|
+
if (this.unsupported.length === 0)
|
|
140
|
+
return;
|
|
141
|
+
const declared = await declaredPlugins({ localConfig: this.localConfig, rootDir: this.rootDir, siteName: this.siteConfig.name });
|
|
142
|
+
const lines = this.unsupported.map(({ label, missing }) => {
|
|
143
|
+
const fix = integrationFix(missing, declared);
|
|
144
|
+
const fixSentence = fix ? ` ${fix}.` : '';
|
|
145
|
+
return `${label}: ${missing.reason} on ${this.siteConfig.name}.${fixSentence}`;
|
|
146
|
+
});
|
|
147
|
+
this.warn(`Skipped with --skip-unsupported, this site can't hold them:\n ${lines.join('\n ')}`);
|
|
148
|
+
}
|
|
100
149
|
}
|
package/dist/commands/push.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { LoopressCommand } from '../lib/base.js';
|
|
|
2
2
|
type PushTargetResult = {
|
|
3
3
|
error?: string;
|
|
4
4
|
label: string;
|
|
5
|
+
reason?: string;
|
|
5
6
|
result?: unknown;
|
|
6
7
|
status: 'failed' | 'pushed' | 'skipped';
|
|
7
8
|
};
|
|
@@ -14,15 +15,24 @@ export default class Push extends LoopressCommand {
|
|
|
14
15
|
static examples: string[];
|
|
15
16
|
static flags: {
|
|
16
17
|
overwrite: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
'skip-unsupported': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
|
+
only: ReturnType<typeof import("@oclif/core/flags").string>;
|
|
20
|
+
skip: ReturnType<typeof import("@oclif/core/flags").string>;
|
|
17
21
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
22
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
23
|
};
|
|
24
|
+
private skipUnsupported;
|
|
25
|
+
private readonly unsupported;
|
|
20
26
|
run(): Promise<PushResult>;
|
|
27
|
+
private applySiteSteps;
|
|
21
28
|
private buildArgv;
|
|
22
|
-
private composerDeclaresPlugins;
|
|
23
29
|
private composerJsonPath;
|
|
30
|
+
private declaredPlugins;
|
|
31
|
+
private doneMessage;
|
|
24
32
|
private globalStylesSkipNote;
|
|
25
33
|
private guardProductionPush;
|
|
26
34
|
private skippedTargets;
|
|
35
|
+
private unsupportedLine;
|
|
36
|
+
private warnUnsupported;
|
|
27
37
|
}
|
|
28
38
|
export {};
|
package/dist/commands/push.js
CHANGED
|
@@ -2,63 +2,84 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { LoopressCommand } from '../lib/base.js';
|
|
5
|
+
import { integrationFix, missingIntegration, missingModule } from '../lib/capabilities.js';
|
|
5
6
|
import { guardProductionPush } from '../lib/guard-production-push.js';
|
|
6
7
|
import { stdoutToStderr } from '../lib/json-delegation.js';
|
|
8
|
+
import { errorMessage } from '../lib/narrow.js';
|
|
7
9
|
import { PushCommand } from '../lib/push-command.js';
|
|
10
|
+
import { parseSelectionFlag, selectionFlags, selectResources } from '../lib/resource-selection.js';
|
|
8
11
|
import { isLocalEnvironment } from '../lib/wp-client.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
12
|
+
import { pinActive } from '../types/plugin.js';
|
|
13
|
+
import { parseComposerJson, wpackagistRequire } from '../utils/composer.js';
|
|
14
|
+
import { projectHasChildThemeFiles, projectUses, resolveResourceDir } from '../utils/resource-dirs.js';
|
|
11
15
|
import { globalStylesSkipNote } from '../utils/theme-styles-format.js';
|
|
12
16
|
// Backed by a plugin the site may not have (ACF, a form or a snippet plugin): a project without
|
|
13
17
|
// the resource's directory doesn't use it, and its push, dry run included, would only stop on the
|
|
14
18
|
// site's 409 or the missing directory.
|
|
15
19
|
const PLUGIN_BACKED = { 'acf:push': 'acf', 'form:push': 'form', 'snippet:push': 'snippets' };
|
|
20
|
+
// Features of Loopress Full only: on Light, skipped while the project doesn't use them, refused
|
|
21
|
+
// upfront when it does. Like every other resource, skipped when the project doesn't use them.
|
|
22
|
+
const FULL_ONLY = { 'api:push': 'api', 'block:push': 'block', 'hook:push': 'hooks', 'page:push': 'page', 'pattern:push': 'pattern' };
|
|
23
|
+
// Always available on the site: nothing to refuse, but a project without the resource's
|
|
24
|
+
// directory has nothing to push, so the step is skipped instead of running on an empty directory.
|
|
25
|
+
const CORE_BACKED = { 'cpt:push': 'cpt', 'menu:push': 'menu', 'meta:push': 'meta', 'option:push': 'options', 'role:push': 'role', 'taxonomy:push': 'taxonomy' };
|
|
16
26
|
// Dependency-ish order: code first (plugins, composer), then the ACF definitions content
|
|
17
27
|
// relies on, then content itself.
|
|
18
28
|
const PUSH_TARGETS = [
|
|
19
|
-
{ commandId: 'plugin:push', label: 'plugins' },
|
|
20
|
-
{ commandId: 'composer:push', label: 'composer' },
|
|
29
|
+
{ commandId: 'plugin:push', id: 'plugin', label: 'plugins' },
|
|
30
|
+
{ commandId: 'composer:push', id: 'composer', label: 'composer' },
|
|
21
31
|
// Before ACF: a field group's location rule can target a Loopress post type or taxonomy.
|
|
22
|
-
{ commandId: 'cpt:push', label: 'post types' },
|
|
23
|
-
{ commandId: 'taxonomy:push', label: 'taxonomies' },
|
|
24
|
-
{ commandId: 'meta:push', label: 'post meta' },
|
|
25
|
-
{ commandId: 'role:push', label: 'roles' },
|
|
26
|
-
{ commandId: 'acf:push', label: 'ACF' },
|
|
27
|
-
{ commandId: 'api:push', label: 'API routes' },
|
|
28
|
-
{ commandId: 'hook:push', label: 'hooks' },
|
|
29
|
-
|
|
32
|
+
{ commandId: 'cpt:push', id: 'cpt', label: 'post types' },
|
|
33
|
+
{ commandId: 'taxonomy:push', id: 'taxonomy', label: 'taxonomies' },
|
|
34
|
+
{ commandId: 'meta:push', id: 'meta', label: 'post meta' },
|
|
35
|
+
{ commandId: 'role:push', id: 'role', label: 'roles' },
|
|
36
|
+
{ commandId: 'acf:push', id: 'acf', label: 'ACF' },
|
|
37
|
+
{ commandId: 'api:push', id: 'api', label: 'API routes' },
|
|
38
|
+
{ commandId: 'hook:push', id: 'hook', label: 'hooks' },
|
|
39
|
+
// Before templates and pages, which place them (`wp:loopress/<slug>`).
|
|
40
|
+
{ commandId: 'block:push', id: 'block', label: 'dynamic blocks' },
|
|
41
|
+
{ commandId: 'form:push', id: 'form', label: 'forms' },
|
|
30
42
|
// Before templates and pages, which can reference them (`wp:pattern`).
|
|
31
|
-
{ commandId: 'pattern:push', label: 'patterns' },
|
|
43
|
+
{ commandId: 'pattern:push', id: 'pattern', label: 'patterns' },
|
|
32
44
|
// Before pages: WordPress refuses a page whose `template` it doesn't know yet.
|
|
33
|
-
{ commandId: 'theme:template:push', label: 'templates and parts' },
|
|
45
|
+
{ commandId: 'theme:template:push', id: 'template', label: 'templates and parts' },
|
|
34
46
|
// Belong to whichever theme is active, so right after its templates. Theme versions stay out:
|
|
35
47
|
// installing or removing themes is `lps theme push`'s call.
|
|
36
|
-
{ commandId: 'theme:style:push', label: 'Global Styles' },
|
|
37
|
-
{ commandId: 'page:push', label: 'pages' },
|
|
38
|
-
{ commandId: 'menu:push', label: 'menus' },
|
|
39
|
-
{ commandId: 'option:push', label: 'options' },
|
|
40
|
-
{ commandId: 'snippet:push', label: 'snippets' },
|
|
48
|
+
{ commandId: 'theme:style:push', id: 'theme-style', label: 'Global Styles' },
|
|
49
|
+
{ commandId: 'page:push', id: 'page', label: 'pages' },
|
|
50
|
+
{ commandId: 'menu:push', id: 'menu', label: 'menus' },
|
|
51
|
+
{ commandId: 'option:push', id: 'option', label: 'options' },
|
|
52
|
+
{ commandId: 'snippet:push', id: 'snippet', label: 'snippets' },
|
|
41
53
|
];
|
|
54
|
+
const PUSH_IDS = PUSH_TARGETS.map((target) => target.id);
|
|
42
55
|
export default class Push extends LoopressCommand {
|
|
43
|
-
static description = 'Push every local resource to WordPress in one run: plugins, composer dependencies, post types, taxonomies, post meta, roles, ACF, API routes, hooks, forms, patterns, templates and parts, Global Styles, pages, menus, options, and snippets';
|
|
56
|
+
static description = 'Push every local resource to WordPress in one run: plugins, composer dependencies, post types, taxonomies, post meta, roles, ACF, API routes, hooks, dynamic blocks, forms, patterns, templates and parts, Global Styles, pages, menus, options, and snippets';
|
|
44
57
|
static enableJsonFlag = true;
|
|
45
|
-
static examples = ['$ lps push', '$ lps push --env staging', '$ lps push --dry-run'];
|
|
58
|
+
static examples = ['$ lps push', '$ lps push --env staging', '$ lps push --dry-run', '$ lps push --only acf,page', '$ lps push --skip plugin --skip composer'];
|
|
46
59
|
static flags = {
|
|
47
60
|
...LoopressCommand.dryRunFlag,
|
|
48
61
|
...LoopressCommand.yesFlag,
|
|
62
|
+
...selectionFlags('push', PUSH_IDS),
|
|
63
|
+
...LoopressCommand.skipUnsupportedFlag,
|
|
49
64
|
overwrite: PushCommand.baseFlags.overwrite,
|
|
50
65
|
};
|
|
66
|
+
skipUnsupported = false;
|
|
67
|
+
// What --skip-unsupported held back, for the final warning.
|
|
68
|
+
unsupported = [];
|
|
51
69
|
async run() {
|
|
52
70
|
await this.guardProductionPush();
|
|
53
|
-
this.
|
|
71
|
+
const { flags } = await this.parse(Push);
|
|
72
|
+
this.skipUnsupported = flags['skip-unsupported'];
|
|
73
|
+
const skipFlag = parseSelectionFlag(flags.skip, 'skip', PUSH_IDS, (message) => this.error(message));
|
|
74
|
+
const selected = new Set(selectResources(PUSH_IDS, parseSelectionFlag(flags.only, 'only', PUSH_IDS, (message) => this.error(message)), skipFlag));
|
|
54
75
|
const argv = this.buildArgv();
|
|
55
76
|
const results = [];
|
|
56
|
-
const skips = await this.skippedTargets();
|
|
77
|
+
const skips = await this.skippedTargets(selected, skipFlag.includes('composer'));
|
|
57
78
|
for (const target of PUSH_TARGETS) {
|
|
58
79
|
const skip = skips.get(target.commandId);
|
|
59
80
|
if (skip) {
|
|
60
81
|
this.log(`\n→ ${target.label}: ${skip.note}`);
|
|
61
|
-
results.push({ label: target.label, status: skip.status });
|
|
82
|
+
results.push({ label: target.label, ...(skip.reason && { reason: skip.reason }), status: skip.status });
|
|
62
83
|
continue;
|
|
63
84
|
}
|
|
64
85
|
this.log(`\n→ Pushing ${target.label}...`);
|
|
@@ -68,19 +89,48 @@ export default class Push extends LoopressCommand {
|
|
|
68
89
|
results.push({ label: target.label, result, status: 'pushed' });
|
|
69
90
|
}
|
|
70
91
|
catch (error) {
|
|
71
|
-
const
|
|
92
|
+
const message = errorMessage(error);
|
|
72
93
|
this.log(`✗ ${target.label} failed: ${message}`);
|
|
73
94
|
results.push({ error: message, label: target.label, status: 'failed' });
|
|
74
95
|
// Stop here: later targets depend on earlier ones (hooks use what composer installs,
|
|
75
96
|
// pages need their templates), so pushing them anyway can ship a silently broken site.
|
|
76
|
-
const skipped = PUSH_TARGETS.slice(PUSH_TARGETS.indexOf(target) + 1)
|
|
97
|
+
const skipped = PUSH_TARGETS.slice(PUSH_TARGETS.indexOf(target) + 1)
|
|
98
|
+
.filter((next) => !skips.has(next.commandId))
|
|
99
|
+
.map((next) => next.label);
|
|
77
100
|
const skippedNote = skipped.length > 0 ? ` Not attempted: ${skipped.join(', ')}.` : '';
|
|
101
|
+
this.warnUnsupported();
|
|
78
102
|
this.error(`${target.label} failed to push: ${message}.${skippedNote}`);
|
|
79
103
|
}
|
|
80
104
|
}
|
|
81
|
-
this.log(this.
|
|
105
|
+
this.log(this.doneMessage(selected.size < PUSH_IDS.length));
|
|
106
|
+
this.warnUnsupported();
|
|
82
107
|
return { results };
|
|
83
108
|
}
|
|
109
|
+
// A step the project uses on a site that can't hold it would only fail at its turn, after
|
|
110
|
+
// everything before it was pushed: refuse the whole run upfront, saying how to fix it. An unused
|
|
111
|
+
// one is skipped, saying why when the site lacks it.
|
|
112
|
+
applySiteSteps(steps, skips, declared) {
|
|
113
|
+
const blockers = [];
|
|
114
|
+
for (const { commandId, missing, skipUnused, used } of steps) {
|
|
115
|
+
if (used && missing) {
|
|
116
|
+
const line = this.unsupportedLine(commandId, missing, declared);
|
|
117
|
+
if (this.skipUnsupported) {
|
|
118
|
+
skips.set(commandId, { note: `${missing.reason}, skipped (--skip-unsupported)`, reason: `${missing.reason} on ${this.siteConfig.name}`, status: 'skipped' });
|
|
119
|
+
this.unsupported.push(line);
|
|
120
|
+
}
|
|
121
|
+
else
|
|
122
|
+
blockers.push(line);
|
|
123
|
+
}
|
|
124
|
+
else if (!used && missing?.needsFull) {
|
|
125
|
+
skips.set(commandId, { note: `${missing.reason}, skipped`, status: 'skipped' });
|
|
126
|
+
}
|
|
127
|
+
else if (!used && skipUnused) {
|
|
128
|
+
skips.set(commandId, { note: 'not configured, skipped', status: 'skipped' });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (blockers.length > 0)
|
|
132
|
+
this.error(`Nothing was pushed, the project uses what this site can't hold:\n ${blockers.join('\n ')}`);
|
|
133
|
+
}
|
|
84
134
|
// Every delegated push always gets --yes: either this command's own guard already confirmed
|
|
85
135
|
// production once above, or the target isn't production and there's nothing to confirm.
|
|
86
136
|
// Without this, each delegated command would re-run its own production guard.
|
|
@@ -93,23 +143,28 @@ export default class Push extends LoopressCommand {
|
|
|
93
143
|
argv.push('--overwrite');
|
|
94
144
|
return argv;
|
|
95
145
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
146
|
+
composerJsonPath() {
|
|
147
|
+
return join(process.cwd(), this.rootDir, 'composer.json');
|
|
148
|
+
}
|
|
149
|
+
// The wordpress.org plugins the project declares: composer.json's wpackagist plugins when it
|
|
150
|
+
// exists, loopress.json's otherwise. Unreadable or invalid composer.json: none, the commands report it.
|
|
151
|
+
async declaredPlugins(hasComposerJson) {
|
|
152
|
+
if (!hasComposerJson)
|
|
153
|
+
return Object.keys(this.localConfig.plugins ?? {});
|
|
102
154
|
try {
|
|
103
|
-
const composerJson =
|
|
104
|
-
return Object.keys(wpackagistRequire(composerJson, 'plugin', isLocalEnvironment(this.siteConfig.name)))
|
|
155
|
+
const composerJson = parseComposerJson(await readFile(this.composerJsonPath(), 'utf8'));
|
|
156
|
+
return Object.keys(wpackagistRequire(composerJson, 'plugin', isLocalEnvironment(this.siteConfig.name)));
|
|
105
157
|
}
|
|
106
158
|
catch {
|
|
107
|
-
|
|
108
|
-
return false;
|
|
159
|
+
return [];
|
|
109
160
|
}
|
|
110
161
|
}
|
|
111
|
-
|
|
112
|
-
|
|
162
|
+
// A run narrowed by --only / --skip must not read as a full push. Unused resources skipped
|
|
163
|
+
// on a plain run don't count: that run still pushes everything the project has.
|
|
164
|
+
doneMessage(narrowed) {
|
|
165
|
+
if (narrowed)
|
|
166
|
+
return this.dryRun ? '\nEvery selected resource would push, the others are skipped.' : '\nSelected resources pushed, the others are skipped.';
|
|
167
|
+
return this.dryRun ? '\nEvery resource would push.' : '\nAll resources pushed.';
|
|
113
168
|
}
|
|
114
169
|
// The site unreachable or the theme list refused: let the step run and report it itself.
|
|
115
170
|
async globalStylesSkipNote() {
|
|
@@ -132,26 +187,80 @@ export default class Push extends LoopressCommand {
|
|
|
132
187
|
}
|
|
133
188
|
// Unconfigured is not a failure: a project that doesn't manage plugins or composer through
|
|
134
189
|
// Loopress must not stop the run before anything else gets pushed.
|
|
135
|
-
|
|
190
|
+
// `composerSkipped`: `composer` was named in --skip, as opposed to just left out of --only.
|
|
191
|
+
async skippedTargets(selected, composerSkipped) {
|
|
136
192
|
const skips = new Map();
|
|
193
|
+
const isSelected = (commandId) => selected.has(PUSH_TARGETS.find((target) => target.commandId === commandId)?.id ?? '');
|
|
137
194
|
const notConfigured = { note: 'not configured, skipped', status: 'skipped' };
|
|
138
195
|
const hasComposerJson = existsSync(this.composerJsonPath());
|
|
139
|
-
const
|
|
196
|
+
const declaredPlugins = await this.declaredPlugins(hasComposerJson);
|
|
197
|
+
// A composer.json that declares plugins is pushed whole by `plugin:push` (with its activation
|
|
198
|
+
// safety), so `composer:push` would only run Composer on the server a second time.
|
|
199
|
+
const composerPushedByPlugins = hasComposerJson && declaredPlugins.length > 0 && isSelected('plugin:push');
|
|
200
|
+
// That push runs Composer on the server, so a run told to skip Composer can't push plugins.
|
|
201
|
+
if (composerPushedByPlugins && composerSkipped) {
|
|
202
|
+
this.error('--skip composer conflicts with pushing plugins: composer.json declares them, so plugin push runs Composer on the server. Skip plugin too, or drop --skip composer.');
|
|
203
|
+
}
|
|
140
204
|
if (composerPushedByPlugins)
|
|
141
205
|
skips.set('composer:push', { note: 'already pushed whole by plugins', status: 'pushed' });
|
|
142
206
|
else if (!hasComposerJson)
|
|
143
207
|
skips.set('composer:push', notConfigured);
|
|
144
208
|
// With a composer.json, plugins come from it only; otherwise from loopress.json.
|
|
145
|
-
|
|
146
|
-
if (!pluginsConfigured)
|
|
209
|
+
if (declaredPlugins.length === 0)
|
|
147
210
|
skips.set('plugin:push', notConfigured);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
211
|
+
const capabilities = await this.capabilities();
|
|
212
|
+
// Installed and activated by this run's first step, before the steps that need them: a fresh
|
|
213
|
+
// site (CI, a new staging) gets ACF from loopress.json and its field groups in one push.
|
|
214
|
+
// composer.json records no active state, so its plugins stay inactive until --activate.
|
|
215
|
+
const activatedByPush = hasComposerJson || !isSelected('plugin:push') ? [] : declaredPlugins.filter((slug) => pinActive(this.localConfig.plugins?.[slug]));
|
|
216
|
+
const missingDependencies = missingModule('dependencies', 'plugins and Composer', capabilities);
|
|
217
|
+
// A target left out by --only / --skip is reported skipped and never reaches the site checks,
|
|
218
|
+
// so it can't block the run.
|
|
219
|
+
const allSteps = [
|
|
220
|
+
...['plugin:push', 'composer:push'].map((commandId) => ({ commandId, missing: missingDependencies, skipUnused: false, used: !skips.has(commandId) })),
|
|
221
|
+
...Object.entries(PLUGIN_BACKED).map(([commandId, kind]) => {
|
|
222
|
+
const missing = missingIntegration(kind, capabilities);
|
|
223
|
+
// Not missing when one of the plugins that would provide it is activated earlier in the run.
|
|
224
|
+
const activated = missing?.plugins?.some((slug) => activatedByPush.includes(slug)) ?? false;
|
|
225
|
+
// A dry run activates nothing, so the site can't preview this step yet.
|
|
226
|
+
if (activated && this.dryRun)
|
|
227
|
+
skips.set(commandId, { note: `${missing?.reason} until this push activates it, skipped`, status: 'skipped' });
|
|
228
|
+
return { commandId, missing: activated ? undefined : missing, skipUnused: true, used: projectUses(kind, this.localConfig) };
|
|
229
|
+
}),
|
|
230
|
+
...Object.entries(FULL_ONLY).map(([commandId, kind]) => ({
|
|
231
|
+
commandId,
|
|
232
|
+
missing: missingIntegration(kind, capabilities),
|
|
233
|
+
skipUnused: true,
|
|
234
|
+
used: projectUses(kind, this.localConfig),
|
|
235
|
+
})),
|
|
236
|
+
...Object.entries(CORE_BACKED).map(([commandId, kind]) => ({ commandId, missing: undefined, skipUnused: true, used: projectUses(kind, this.localConfig) })),
|
|
237
|
+
// Without templates, parts or CSS, the step would only write an empty child theme.
|
|
238
|
+
{ commandId: 'theme:template:push', missing: missingIntegration('template', capabilities), skipUnused: true, used: projectHasChildThemeFiles(this.localConfig) },
|
|
239
|
+
];
|
|
240
|
+
const steps = allSteps.filter((step) => isSelected(step.commandId));
|
|
241
|
+
this.applySiteSteps(steps, skips, { fromComposer: hasComposerJson, plugins: declaredPlugins });
|
|
242
|
+
const stylesNote = isSelected('theme:style:push') ? await this.globalStylesSkipNote() : undefined;
|
|
153
243
|
if (stylesNote)
|
|
154
244
|
skips.set('theme:style:push', { note: stylesNote, status: 'skipped' });
|
|
245
|
+
for (const target of PUSH_TARGETS) {
|
|
246
|
+
// Composer left out of --only still runs when plugin push syncs composer.json: say so.
|
|
247
|
+
if (composerPushedByPlugins && target.commandId === 'composer:push')
|
|
248
|
+
continue;
|
|
249
|
+
if (!selected.has(target.id))
|
|
250
|
+
skips.set(target.commandId, { note: 'not selected, skipped', status: 'skipped' });
|
|
251
|
+
}
|
|
155
252
|
return skips;
|
|
156
253
|
}
|
|
254
|
+
// "<resources>: <why> on <site>. <fix>", for the refusal and for the --skip-unsupported warning.
|
|
255
|
+
unsupportedLine(commandId, missing, declared) {
|
|
256
|
+
const label = PUSH_TARGETS.find((target) => target.commandId === commandId)?.label;
|
|
257
|
+
const fix = integrationFix(missing, declared);
|
|
258
|
+
const fixSentence = fix ? ` ${fix}.` : '';
|
|
259
|
+
return `${label}: ${missing.reason} on ${this.siteConfig.name}.${fixSentence}`;
|
|
260
|
+
}
|
|
261
|
+
warnUnsupported() {
|
|
262
|
+
if (this.unsupported.length > 0) {
|
|
263
|
+
this.warn(`Skipped with --skip-unsupported, this site can't hold them:\n ${this.unsupported.join('\n ')}\nResources that depend on them (a page placing a block, code calling a hook) may not work until they are pushed.`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
157
266
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Args, Command } from '@oclif/core';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
2
|
import { ApiClient } from '../../lib/api-client.js';
|
|
4
3
|
import { loadSnippets } from '../../lib/load-snippets.js';
|
|
4
|
+
import { errorMessage } from '../../lib/narrow.js';
|
|
5
5
|
import { resolveLinkedProject } from '../../lib/resolve-linked-project.js';
|
|
6
|
+
import {} from '../../types/snippet.js';
|
|
6
7
|
import { pluralize } from '../../utils/pluralize.js';
|
|
8
|
+
import { resolveResourceDir } from '../../utils/resource-dirs.js';
|
|
7
9
|
import { toSlug } from '../../utils/to-slug.js';
|
|
8
10
|
// Publishes to the Loopress api (not a WordPress site), so this does not extend
|
|
9
11
|
// `LoopressCommand`/`PushCommand`: those force an environment to be resolved, but a project
|
|
@@ -18,14 +20,16 @@ export default class Publish extends Command {
|
|
|
18
20
|
async run() {
|
|
19
21
|
const { args } = await this.parse(Publish);
|
|
20
22
|
const { apiProjectId, localConfig, token } = await resolveLinkedProject((message) => this.error(message));
|
|
21
|
-
const path =
|
|
23
|
+
const path = resolveResourceDir('snippets', localConfig, args.path);
|
|
22
24
|
this.log(`Publishing snippets from ${path}`);
|
|
23
25
|
let snippets;
|
|
24
26
|
try {
|
|
25
|
-
snippets = await loadSnippets(path)
|
|
27
|
+
snippets = await loadSnippets(path, (message) => {
|
|
28
|
+
this.warn(message);
|
|
29
|
+
});
|
|
26
30
|
}
|
|
27
31
|
catch (error) {
|
|
28
|
-
this.error(error
|
|
32
|
+
this.error(errorMessage(error));
|
|
29
33
|
}
|
|
30
34
|
const api = new ApiClient(token);
|
|
31
35
|
try {
|
|
@@ -34,7 +38,7 @@ export default class Publish extends Command {
|
|
|
34
38
|
});
|
|
35
39
|
}
|
|
36
40
|
catch (error) {
|
|
37
|
-
this.error(error
|
|
41
|
+
this.error(errorMessage(error));
|
|
38
42
|
}
|
|
39
43
|
this.log(`Published ${pluralize(snippets.length, 'snippet')} to your Loopress account.`);
|
|
40
44
|
}
|