@loopress/cli 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1460 -337
- 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/cpt/diff.js +5 -0
- package/dist/commands/cpt/list.d.ts +2 -0
- package/dist/commands/cpt/list.js +4 -0
- package/dist/commands/cpt/pull.d.ts +2 -0
- package/dist/commands/cpt/pull.js +4 -0
- package/dist/commands/cpt/push.d.ts +2 -0
- package/dist/commands/cpt/push.js +4 -0
- package/dist/commands/cpt/rm.d.ts +2 -0
- package/dist/commands/cpt/rm.js +3 -0
- package/dist/commands/cpt/rollback.js +5 -0
- package/dist/commands/dev.js +14 -4
- package/dist/commands/diff.js +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +30 -2
- package/dist/commands/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/meta/diff.d.ts +2 -0
- package/dist/commands/meta/diff.js +5 -0
- package/dist/commands/meta/list.d.ts +19 -0
- package/dist/commands/meta/list.js +21 -0
- package/dist/commands/meta/pull.d.ts +2 -0
- package/dist/commands/meta/pull.js +4 -0
- package/dist/commands/meta/push.d.ts +2 -0
- package/dist/commands/meta/push.js +4 -0
- package/dist/commands/meta/rm.d.ts +2 -0
- package/dist/commands/meta/rm.js +3 -0
- package/dist/commands/meta/rollback.d.ts +2 -0
- package/dist/commands/meta/rollback.js +5 -0
- package/dist/commands/option/list.js +2 -8
- package/dist/commands/option/pull.js +4 -4
- package/dist/commands/option/push.js +2 -0
- package/dist/commands/page/push.js +3 -0
- package/dist/commands/pattern/diff.d.ts +2 -0
- package/dist/commands/pattern/diff.js +5 -0
- package/dist/commands/pattern/list.d.ts +2 -0
- package/dist/commands/pattern/list.js +3 -0
- package/dist/commands/pattern/pull.d.ts +2 -0
- package/dist/commands/pattern/pull.js +4 -0
- package/dist/commands/pattern/push.d.ts +2 -0
- package/dist/commands/pattern/push.js +4 -0
- package/dist/commands/pattern/rm.d.ts +2 -0
- package/dist/commands/pattern/rm.js +3 -0
- package/dist/commands/pattern/rollback.d.ts +2 -0
- package/dist/commands/pattern/rollback.js +5 -0
- package/dist/commands/plugin/audit.js +2 -1
- package/dist/commands/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/project/config.d.ts +1 -0
- package/dist/commands/project/config.js +20 -1
- package/dist/commands/project/list.d.ts +1 -1
- package/dist/commands/project/list.js +6 -6
- package/dist/commands/project/pull.js +11 -8
- package/dist/commands/project/push.js +23 -18
- package/dist/commands/project/remove.js +1 -1
- package/dist/commands/project/switch.d.ts +0 -1
- package/dist/commands/project/switch.js +16 -36
- package/dist/commands/pull.js +7 -2
- package/dist/commands/push.js +14 -6
- package/dist/commands/role/diff.d.ts +2 -0
- package/dist/commands/role/diff.js +5 -0
- package/dist/commands/role/list.d.ts +2 -0
- package/dist/commands/role/list.js +4 -0
- package/dist/commands/role/pull.d.ts +2 -0
- package/dist/commands/role/pull.js +4 -0
- package/dist/commands/role/push.d.ts +2 -0
- package/dist/commands/role/push.js +4 -0
- package/dist/commands/role/rm.d.ts +2 -0
- package/dist/commands/role/rm.js +3 -0
- package/dist/commands/role/rollback.d.ts +2 -0
- package/dist/commands/role/rollback.js +5 -0
- package/dist/commands/snippet/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 +3 -3
- package/dist/commands/status.js +21 -53
- package/dist/commands/taxonomy/diff.d.ts +2 -0
- package/dist/commands/taxonomy/diff.js +5 -0
- package/dist/commands/taxonomy/list.d.ts +2 -0
- package/dist/commands/taxonomy/list.js +5 -0
- package/dist/commands/taxonomy/pull.d.ts +2 -0
- package/dist/commands/taxonomy/pull.js +4 -0
- package/dist/commands/taxonomy/push.d.ts +2 -0
- package/dist/commands/taxonomy/push.js +4 -0
- package/dist/commands/taxonomy/rm.d.ts +2 -0
- package/dist/commands/taxonomy/rm.js +3 -0
- package/dist/commands/taxonomy/rollback.d.ts +2 -0
- package/dist/commands/taxonomy/rollback.js +5 -0
- package/dist/commands/theme/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 +3 -1
- package/dist/commands/theme/template/push.js +65 -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/config/project-config.manager.d.ts +3 -3
- package/dist/config/project-config.manager.js +22 -31
- package/dist/hooks/finally.d.ts +2 -1
- package/dist/hooks/finally.js +38 -7
- package/dist/lib/api-client.js +2 -1
- package/dist/lib/app-build.js +3 -1
- package/dist/lib/app-manifest.js +2 -2
- package/dist/lib/base.d.ts +2 -2
- package/dist/lib/base.js +33 -38
- package/dist/lib/declared-type-command.d.ts +35 -0
- package/dist/lib/declared-type-command.js +192 -0
- package/dist/lib/dev-watch.d.ts +1 -1
- package/dist/lib/dev-watch.js +7 -3
- package/dist/lib/diff-state.js +1 -0
- package/dist/lib/github-release.js +4 -1
- package/dist/lib/local-callback-server.d.ts +12 -6
- package/dist/lib/local-callback-server.js +24 -23
- package/dist/lib/php-files-command.js +3 -1
- package/dist/lib/push-command.d.ts +6 -0
- package/dist/lib/push-command.js +61 -18
- package/dist/lib/put-or-create.js +3 -4
- package/dist/lib/resource-state.js +40 -86
- package/dist/lib/rollback-materialize.js +13 -24
- package/dist/lib/snapshot-store.js +2 -2
- package/dist/lib/spinner.d.ts +2 -0
- package/dist/lib/spinner.js +72 -0
- package/dist/lib/temp-admin.d.ts +10 -1
- package/dist/lib/temp-admin.js +27 -2
- package/dist/lib/validate-local.js +31 -9
- package/dist/lib/wp-authorize-flow.d.ts +10 -17
- package/dist/lib/wp-authorize-flow.js +24 -28
- package/dist/lib/wp-client.d.ts +1 -0
- package/dist/lib/wp-client.js +7 -1
- package/dist/lib/wp-site-diagnostic.js +39 -1
- package/dist/types/global-config.generated.d.ts +1 -5
- package/dist/types/project-config.generated.d.ts +28 -4
- package/dist/utils/composer.d.ts +3 -1
- package/dist/utils/composer.js +16 -7
- package/dist/utils/declared-type-format.d.ts +52 -0
- package/dist/utils/declared-type-format.js +233 -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 +5 -1
- package/dist/utils/page-format.js +7 -3
- package/dist/utils/plugin-sync.js +1 -1
- package/dist/utils/resource-dirs.d.ts +9 -3
- package/dist/utils/resource-dirs.js +18 -6
- package/dist/utils/template-format.d.ts +3 -1
- package/dist/utils/template-format.js +10 -3
- package/dist/utils/theme-css.d.ts +19 -0
- package/dist/utils/theme-css.js +100 -0
- package/oclif.manifest.json +3542 -1087
- package/package.json +40 -21
- package/schemas/global-config.schema.json +2 -6
- package/schemas/project-config.schema.json +37 -7
- package/dist/commands/seo/diff.js +0 -5
- package/dist/commands/seo/list.d.ts +0 -18
- package/dist/commands/seo/list.js +0 -58
- package/dist/commands/seo/pull.d.ts +0 -17
- package/dist/commands/seo/pull.js +0 -87
- package/dist/commands/seo/push.d.ts +0 -23
- package/dist/commands/seo/push.js +0 -176
- package/dist/commands/seo/rollback.js +0 -5
- package/dist/commands/template/push.js +0 -42
- package/dist/utils/seo-format.d.ts +0 -29
- package/dist/utils/seo-format.js +0 -18
- /package/dist/commands/{seo → cpt}/diff.d.ts +0 -0
- /package/dist/commands/{seo → cpt}/rollback.d.ts +0 -0
package/bin/dev.js
CHANGED
|
File without changes
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
import { type AcfObjectType } from '../../utils/acf-format.js';
|
|
3
|
+
type AcfPullResult = {
|
|
4
|
+
path: string;
|
|
5
|
+
status: 'dry-run' | 'success';
|
|
6
|
+
types: AcfObjectType[];
|
|
7
|
+
};
|
|
2
8
|
export default class Pull extends LoopressCommand {
|
|
3
9
|
static args: {
|
|
4
10
|
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
11
|
};
|
|
6
12
|
static description: string;
|
|
13
|
+
static enableJsonFlag: boolean;
|
|
7
14
|
static examples: string[];
|
|
8
15
|
static flags: {
|
|
9
16
|
type: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
17
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
18
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
19
|
};
|
|
13
|
-
run(): Promise<
|
|
20
|
+
run(): Promise<AcfPullResult>;
|
|
14
21
|
private pullType;
|
|
15
22
|
}
|
|
23
|
+
export {};
|
|
@@ -9,6 +9,7 @@ export default class Pull extends LoopressCommand {
|
|
|
9
9
|
path: Args.string({ description: 'Path to ACF directory (overrides project config)' }),
|
|
10
10
|
};
|
|
11
11
|
static description = 'Pull ACF field groups, post types, taxonomies, and options pages from WordPress';
|
|
12
|
+
static enableJsonFlag = true;
|
|
12
13
|
static examples = ['$ lps acf pull', '$ lps acf pull --type field-groups'];
|
|
13
14
|
static flags = {
|
|
14
15
|
...LoopressCommand.dryRunFlag,
|
|
@@ -25,6 +26,7 @@ export default class Pull extends LoopressCommand {
|
|
|
25
26
|
for (const type of types) {
|
|
26
27
|
await this.pullType(type, path);
|
|
27
28
|
}
|
|
29
|
+
return { path, status: this.dryRun ? 'dry-run' : 'success', types };
|
|
28
30
|
}
|
|
29
31
|
async pullType(type, basePath) {
|
|
30
32
|
const dir = join(basePath, type);
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import { PushCommand } from '../../lib/push-command.js';
|
|
2
|
+
import { type AcfObjectType } from '../../utils/acf-format.js';
|
|
3
|
+
type AcfPushResult = {
|
|
4
|
+
path: string;
|
|
5
|
+
status: 'dry-run' | 'success';
|
|
6
|
+
types: AcfObjectType[];
|
|
7
|
+
};
|
|
2
8
|
export default class Push extends PushCommand {
|
|
3
9
|
static args: {
|
|
4
10
|
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
11
|
};
|
|
6
12
|
static description: string;
|
|
13
|
+
static enableJsonFlag: boolean;
|
|
7
14
|
static examples: string[];
|
|
8
15
|
static flags: {
|
|
9
16
|
type: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
17
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
18
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
19
|
};
|
|
13
|
-
run(): Promise<
|
|
20
|
+
run(): Promise<AcfPushResult>;
|
|
14
21
|
private currentRevision;
|
|
15
22
|
private loadObjects;
|
|
16
23
|
private pushObject;
|
|
17
24
|
private pushType;
|
|
18
25
|
}
|
|
26
|
+
export {};
|
|
@@ -11,6 +11,7 @@ export default class Push extends PushCommand {
|
|
|
11
11
|
path: Args.string({ description: 'Path to ACF directory (overrides project config)' }),
|
|
12
12
|
};
|
|
13
13
|
static description = 'Push ACF field groups, post types, taxonomies, and options pages to WordPress';
|
|
14
|
+
static enableJsonFlag = true;
|
|
14
15
|
static examples = ['$ lps acf push', '$ lps acf push --type field-groups'];
|
|
15
16
|
static flags = {
|
|
16
17
|
...PushCommand.dryRunFlag,
|
|
@@ -34,9 +35,10 @@ export default class Push extends PushCommand {
|
|
|
34
35
|
this.error(`${pluralize(this.failedCount, 'ACF object')} failed to push.`);
|
|
35
36
|
}
|
|
36
37
|
if (this.dryRun)
|
|
37
|
-
return;
|
|
38
|
+
return { path, status: 'dry-run', types };
|
|
38
39
|
await this.recordSuccess();
|
|
39
40
|
this.log('All ACF objects pushed.');
|
|
41
|
+
return { path, status: 'success', types };
|
|
40
42
|
}
|
|
41
43
|
async currentRevision(type, key) {
|
|
42
44
|
try {
|
|
@@ -16,7 +16,7 @@ export default class Publish extends Command {
|
|
|
16
16
|
path: Args.string({ description: 'Path to api directory (overrides project config)' }),
|
|
17
17
|
};
|
|
18
18
|
static description = 'Publish custom API routes to your Loopress account so they can be deployed to other projects. Does not touch any WordPress site.';
|
|
19
|
-
static examples = ['$ lps api publish', '$ lps api publish
|
|
19
|
+
static examples = ['$ lps api publish', '$ lps api publish ./api'];
|
|
20
20
|
async run() {
|
|
21
21
|
const { args } = await this.parse(Publish);
|
|
22
22
|
const { apiProjectId, localConfig, token } = await resolveLinkedProject((message) => this.error(message));
|
|
@@ -63,7 +63,7 @@ export default class Push extends PushCommand {
|
|
|
63
63
|
.filter((entry) => entry.isDirectory())
|
|
64
64
|
.map((entry) => entry.name)
|
|
65
65
|
.filter((name) => existsSync(join(path, name, APP_CONFIG_FILENAME)))
|
|
66
|
-
.
|
|
66
|
+
.toSorted((a, b) => a.localeCompare(b));
|
|
67
67
|
if (only) {
|
|
68
68
|
if (!names.includes(only)) {
|
|
69
69
|
this.error(`No app "${only}" in ${path} (looked for ${join(only, APP_CONFIG_FILENAME)}).`);
|
|
@@ -3,8 +3,8 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { PushCommand } from '../../lib/push-command.js';
|
|
6
|
-
import { isTimeoutError } from '../../lib/wp-client.js';
|
|
7
|
-
import { toIntent } from '../../utils/composer.js';
|
|
6
|
+
import { isLocalEnvironment, isTimeoutError } from '../../lib/wp-client.js';
|
|
7
|
+
import { requireToInstall, toIntent } from '../../utils/composer.js';
|
|
8
8
|
import { parseCollisions, SYNC_TIMEOUT_MS, } from '../../utils/plugin-sync.js';
|
|
9
9
|
export default class ComposerPush extends PushCommand {
|
|
10
10
|
static description = 'Push composer.json to WordPress and run Composer to resolve and install dependencies';
|
|
@@ -25,7 +25,7 @@ export default class ComposerPush extends PushCommand {
|
|
|
25
25
|
this.error(`No composer.json found at ${composerJsonPath}. Run \`lps composer init\` first.`);
|
|
26
26
|
}
|
|
27
27
|
const parsed = JSON.parse(await readFile(composerJsonPath, 'utf8'));
|
|
28
|
-
const require = parsed.
|
|
28
|
+
const require = requireToInstall(parsed, isLocalEnvironment(this.siteConfig.name));
|
|
29
29
|
const packageCount = Object.keys(require).length;
|
|
30
30
|
const intent = toIntent(require);
|
|
31
31
|
const hasLock = existsSync(composerLockPath);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { resourceDiffCommand } from '../../lib/diff-command.js';
|
|
2
|
+
export default resourceDiffCommand('cpt', {
|
|
3
|
+
description: 'Show what differs in custom post types between your local cpt/ files and a WordPress environment, or between two environments',
|
|
4
|
+
pathNoun: 'cpt directory',
|
|
5
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { declaredListCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { CPT_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
export default declaredListCommand(CPT_SPEC, 'List the post types registered on WordPress and where each comes from (Loopress, WordPress, ACF, CPT UI, a theme or plugin). ' +
|
|
4
|
+
'Only Loopress ones are pulled and pushed.', (count) => `${count} published`);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { declaredPullCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { CPT_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
export default declaredPullCommand(CPT_SPEC, 'Pull the custom post types Loopress manages on WordPress into cpt/<slug>.json files. ' +
|
|
4
|
+
'Post types registered by a theme or another plugin are never pulled, see `lps cpt list`.');
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { declaredPushCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { CPT_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
export default declaredPushCommand(CPT_SPEC, 'Push local custom post types (cpt/<slug>.json, register_post_type() arguments) to WordPress. ' +
|
|
4
|
+
'Create or update only, never deletes a post type: use `lps cpt rm` for that.');
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { declaredRmCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { CPT_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
export default declaredRmCommand(CPT_SPEC, 'Stop registering a Loopress custom post type on WordPress. Its posts are kept in the database, hidden until the post type is pushed again.', "Its posts are kept in the database, hidden until it's pushed again.");
|
package/dist/commands/dev.js
CHANGED
|
@@ -48,18 +48,28 @@ export default class Dev extends Command {
|
|
|
48
48
|
}
|
|
49
49
|
// Sequential on purpose (see notes technique: parallelizing push is out of scope). A failed
|
|
50
50
|
// push is logged and swallowed here, never thrown, so one bad batch can't take the watcher down.
|
|
51
|
+
// Types sharing a command run it once: templates, parts, css and blocks all rewrite the whole
|
|
52
|
+
// child theme through `theme:template:push`.
|
|
51
53
|
async pushBatch(changesByType, targets) {
|
|
54
|
+
const byCommand = new Map();
|
|
52
55
|
for (const [type, paths] of changesByType) {
|
|
53
56
|
const target = targets.find((t) => t.type === type);
|
|
54
57
|
if (!target)
|
|
55
58
|
continue;
|
|
56
|
-
|
|
59
|
+
const batch = byCommand.get(target.commandId) ?? { paths: [], types: [] };
|
|
60
|
+
batch.types.push(type);
|
|
61
|
+
batch.paths.push(...paths);
|
|
62
|
+
byCommand.set(target.commandId, batch);
|
|
63
|
+
}
|
|
64
|
+
for (const [commandId, { paths, types }] of byCommand) {
|
|
65
|
+
const label = types.join(', ');
|
|
66
|
+
this.log(`\n→ ${label} changed (${paths.map((path) => basename(path)).join(', ')}), pushing to local...`);
|
|
57
67
|
try {
|
|
58
|
-
await this.config.runCommand(
|
|
59
|
-
this.log(`✓ ${
|
|
68
|
+
await this.config.runCommand(commandId, ['--env', 'local']);
|
|
69
|
+
this.log(`✓ ${label} synced`);
|
|
60
70
|
}
|
|
61
71
|
catch (error) {
|
|
62
|
-
this.log(`✗ ${
|
|
72
|
+
this.log(`✗ ${label} failed: ${error.message}`);
|
|
63
73
|
}
|
|
64
74
|
}
|
|
65
75
|
}
|
package/dist/commands/diff.js
CHANGED
|
@@ -7,7 +7,7 @@ const RESOURCES = [...RESOURCE_STATE_PROVIDERS.map((provider) => provider.resour
|
|
|
7
7
|
export default class Diff extends DiffCommand {
|
|
8
8
|
static description = [
|
|
9
9
|
'Show what differs between your local tracked files and a WordPress environment, or between two environments.',
|
|
10
|
-
'Covers snippets, forms, ACF, API routes, Hooks, templates, template parts, pages,
|
|
10
|
+
'Covers snippets, forms, ACF, API routes, Hooks, templates, template parts, pages, Options, Menus, Theme styles, and Composer. Plugins and themes have their own `lps plugin status` / `lps theme status`.',
|
|
11
11
|
'Exit code: 0 in sync, 1 on drift, 2 when a resource could not be compared, so it doubles as a CI drift gate.',
|
|
12
12
|
].join(' ');
|
|
13
13
|
static enableJsonFlag = true;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { configManager } from '../config/project-config.manager.js';
|
|
2
2
|
import { LoopressCommand } from '../lib/base.js';
|
|
3
|
-
import {
|
|
3
|
+
import { findTempAdmins } from '../lib/temp-admin.js';
|
|
4
|
+
import { isForbiddenError, isNotFoundError } from '../lib/wp-client.js';
|
|
4
5
|
import { diagnoseWpSite } from '../lib/wp-site-diagnostic.js';
|
|
5
6
|
import { pluralize } from '../utils/pluralize.js';
|
|
6
7
|
export default class Doctor extends LoopressCommand {
|
|
@@ -32,7 +33,10 @@ export default class Doctor extends LoopressCommand {
|
|
|
32
33
|
await this.check('Loopress plugin installed (loopress/v1 endpoints)', async () => this.wp.get('loopress/v1'));
|
|
33
34
|
// wp/v2/users/me is WordPress core and requires authentication, so it validates the
|
|
34
35
|
// application password without depending on any Loopress feature.
|
|
35
|
-
await this.check('Credentials accepted (authenticated request)', async () => this.wp.get('wp/v2/users/me'));
|
|
36
|
+
const isAuthenticated = await this.check('Credentials accepted (authenticated request)', async () => this.wp.get('wp/v2/users/me'));
|
|
37
|
+
// Skipped when the credentials were just rejected: it would only repeat that same 401.
|
|
38
|
+
if (isAuthenticated)
|
|
39
|
+
await this.checkLeftoverTempAdmins();
|
|
36
40
|
pluginVersion = await this.reportPluginVersion();
|
|
37
41
|
}
|
|
38
42
|
else {
|
|
@@ -72,6 +76,30 @@ export default class Doctor extends LoopressCommand {
|
|
|
72
76
|
return false;
|
|
73
77
|
}
|
|
74
78
|
}
|
|
79
|
+
// Report only, never deletes: a doctor run shouldn't change the site. A leftover is a live
|
|
80
|
+
// administrator with a password nobody holds, so it fails the run. Credentials without
|
|
81
|
+
// `list_users` can't look, which is informational, not a failure: nothing was found wrong.
|
|
82
|
+
async checkLeftoverTempAdmins() {
|
|
83
|
+
let lookup;
|
|
84
|
+
try {
|
|
85
|
+
lookup = { leftovers: await findTempAdmins(this.wp) };
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
lookup = { error };
|
|
89
|
+
}
|
|
90
|
+
if ('error' in lookup && isForbiddenError(lookup.error)) {
|
|
91
|
+
this.out('- Leftover temporary admin accounts: not checked, these credentials cannot list users.');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
await this.check('No leftover temporary admin accounts', async () => {
|
|
95
|
+
if ('error' in lookup)
|
|
96
|
+
throw lookup.error;
|
|
97
|
+
if (lookup.leftovers.length > 0) {
|
|
98
|
+
const names = lookup.leftovers.map((admin) => admin.username).join(', ');
|
|
99
|
+
throw new Error(`Found ${names}, left by an interrupted Loopress Full install. Re-run \`lps project config\` to remove them, or delete them in wp-admin > Users.`);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
75
103
|
out(message) {
|
|
76
104
|
if (!this.jsonEnabled())
|
|
77
105
|
this.log(message);
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
type FormPullResult = {
|
|
3
|
+
orphans: string[];
|
|
4
|
+
path: string;
|
|
5
|
+
skipped: number;
|
|
6
|
+
status: 'dry-run' | 'success';
|
|
7
|
+
};
|
|
2
8
|
export default class Pull extends LoopressCommand {
|
|
3
9
|
static args: {
|
|
4
10
|
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
11
|
};
|
|
6
12
|
static description: string;
|
|
13
|
+
static enableJsonFlag: boolean;
|
|
7
14
|
static examples: string[];
|
|
8
15
|
static flags: {
|
|
9
16
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
17
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
18
|
};
|
|
12
|
-
run(): Promise<
|
|
19
|
+
run(): Promise<FormPullResult>;
|
|
13
20
|
}
|
|
21
|
+
export {};
|
|
@@ -11,6 +11,7 @@ export default class Pull extends LoopressCommand {
|
|
|
11
11
|
path: Args.string({ description: 'Path to forms directory (overrides project config)' }),
|
|
12
12
|
};
|
|
13
13
|
static description = 'Pull forms from WordPress';
|
|
14
|
+
static enableJsonFlag = true;
|
|
14
15
|
static examples = ['$ lps form pull'];
|
|
15
16
|
static flags = {
|
|
16
17
|
...LoopressCommand.dryRunFlag,
|
|
@@ -46,9 +47,10 @@ export default class Pull extends LoopressCommand {
|
|
|
46
47
|
},
|
|
47
48
|
});
|
|
48
49
|
if (this.dryRun)
|
|
49
|
-
return;
|
|
50
|
+
return { orphans, path, skipped, status: 'dry-run' };
|
|
50
51
|
if (skipped > 0) {
|
|
51
52
|
this.warn(`${pluralize(skipped, 'form')} skipped because they have no id`);
|
|
52
53
|
}
|
|
54
|
+
return { orphans, path, skipped, status: 'success' };
|
|
53
55
|
}
|
|
54
56
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { PushCommand } from '../../lib/push-command.js';
|
|
2
|
+
type FormPushResult = {
|
|
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-notifications': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -11,9 +16,10 @@ export default class Push extends PushCommand {
|
|
|
11
16
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
17
|
};
|
|
13
18
|
private allowNotifications;
|
|
14
|
-
run(): Promise<
|
|
19
|
+
run(): Promise<FormPushResult>;
|
|
15
20
|
private currentRevision;
|
|
16
21
|
private ensureCanonicalFilename;
|
|
17
22
|
private loadFiles;
|
|
18
23
|
private pushForm;
|
|
19
24
|
}
|
|
25
|
+
export {};
|
|
@@ -14,6 +14,7 @@ export default class Push extends PushCommand {
|
|
|
14
14
|
path: Args.string({ description: 'Path to forms directory (overrides project config)' }),
|
|
15
15
|
};
|
|
16
16
|
static description = 'Push forms to WordPress. Local files created or updated remotely are renamed on disk to the `<id>-<slug>.json` convention.';
|
|
17
|
+
static enableJsonFlag = true;
|
|
17
18
|
static examples = ['$ lps form push', '$ lps form push --allow-notifications'];
|
|
18
19
|
static flags = {
|
|
19
20
|
...PushCommand.dryRunFlag,
|
|
@@ -41,9 +42,10 @@ export default class Push extends PushCommand {
|
|
|
41
42
|
this.error(`${pluralize(this.failedCount, 'form')} failed to push.`);
|
|
42
43
|
}
|
|
43
44
|
if (this.dryRun)
|
|
44
|
-
return;
|
|
45
|
+
return { path, status: 'dry-run' };
|
|
45
46
|
await this.recordSuccess();
|
|
46
47
|
this.log('All forms pushed.');
|
|
48
|
+
return { path, status: 'success' };
|
|
47
49
|
}
|
|
48
50
|
async currentRevision(id) {
|
|
49
51
|
try {
|
package/dist/commands/init.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { confirm, input, select } from '@inquirer/prompts';
|
|
2
2
|
import { Command } from '@oclif/core';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
@@ -11,19 +11,6 @@ const SNIPPET_PROVIDERS = [
|
|
|
11
11
|
{ name: 'Code Snippets', slug: 'code-snippets' },
|
|
12
12
|
{ name: 'WPCode', slug: 'insert-headers-and-footers' },
|
|
13
13
|
];
|
|
14
|
-
// Default directory per optional feature, matching the resolve*Path defaults in lib/base.ts.
|
|
15
|
-
const FEATURES = [
|
|
16
|
-
{ dir: 'acf', key: 'acfDir', label: 'ACF' },
|
|
17
|
-
{ dir: 'seo', key: 'seoDir', label: 'SEO' },
|
|
18
|
-
{ dir: 'menus', key: 'menuDir', label: 'Menus' },
|
|
19
|
-
{ dir: 'forms', key: 'formDir', label: 'Forms' },
|
|
20
|
-
{ dir: 'api', key: 'apiDir', label: 'Custom API routes' },
|
|
21
|
-
{ dir: 'hooks', key: 'hooksDir', label: 'Hooks (actions, filters, cron)' },
|
|
22
|
-
{ dir: 'apps', key: 'appsDir', label: 'Single-page apps' },
|
|
23
|
-
{ dir: 'pages', key: 'pageDir', label: 'Static pages' },
|
|
24
|
-
{ dir: 'templates', key: 'templateDir', label: 'Block templates' },
|
|
25
|
-
{ dir: 'parts', key: 'partDir', label: 'Block template parts' },
|
|
26
|
-
];
|
|
27
14
|
export default class Init extends Command {
|
|
28
15
|
static description = 'Initialize a loopress.json config file in the current directory';
|
|
29
16
|
static examples = ['$ lps init'];
|
|
@@ -48,27 +35,13 @@ export default class Init extends Command {
|
|
|
48
35
|
default: '.',
|
|
49
36
|
message: 'Root directory',
|
|
50
37
|
});
|
|
51
|
-
const snippetsDir = await input({
|
|
52
|
-
default: 'snippets',
|
|
53
|
-
message: 'Snippets directory (relative to root)',
|
|
54
|
-
});
|
|
55
|
-
const features = await checkbox({
|
|
56
|
-
choices: FEATURES.map((feature) => ({ name: `${feature.label} (${feature.dir}/)`, value: feature.key })),
|
|
57
|
-
message: 'Other features to configure directories for (optional)',
|
|
58
|
-
});
|
|
59
38
|
const providerChoice = await select({
|
|
60
39
|
choices: [...SNIPPET_PROVIDERS.map((p) => ({ name: p.name, value: p.slug })), { name: 'None / already installed', value: '__none__' }],
|
|
61
40
|
message: 'Snippet provider',
|
|
62
41
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
snippetsDir,
|
|
67
|
-
};
|
|
68
|
-
for (const feature of FEATURES) {
|
|
69
|
-
if (features.includes(feature.key))
|
|
70
|
-
config[feature.key] = feature.dir;
|
|
71
|
-
}
|
|
42
|
+
// Resource directories are left out on purpose: every command falls back to its default
|
|
43
|
+
// (utils/resource-dirs.ts), and a custom name is a one-line edit in loopress.json.
|
|
44
|
+
const config = { projectId, rootDir };
|
|
72
45
|
await writeLocalConfig(config);
|
|
73
46
|
const isGitattributesUpdated = await ensureLfGitattributes();
|
|
74
47
|
let isProviderAdded = false;
|
|
@@ -83,19 +56,14 @@ export default class Init extends Command {
|
|
|
83
56
|
}
|
|
84
57
|
this.log(`\n✓ loopress.json created`);
|
|
85
58
|
this.log(` Project: ${projectLabel}`);
|
|
86
|
-
this.log(`
|
|
87
|
-
for (const feature of FEATURES) {
|
|
88
|
-
if (features.includes(feature.key)) {
|
|
89
|
-
this.log(` ${feature.label}:${' '.repeat(Math.max(1, 9 - feature.label.length))}${join(rootDir, feature.dir)}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
59
|
+
this.log(` Root: ${rootDir}`);
|
|
92
60
|
if (isProviderAdded) {
|
|
93
61
|
this.log(` Plugin: ${providerChoice}`);
|
|
94
62
|
}
|
|
95
63
|
if (isGitattributesUpdated) {
|
|
96
64
|
this.log('✓ .gitattributes: LF line endings, so files match across macOS, Linux and Windows');
|
|
97
65
|
}
|
|
98
|
-
this.log('\n→ Next: run `lps
|
|
66
|
+
this.log('\n→ Next: run `lps pull` to fetch what is already on the site, or `lps doctor` to verify the connection.');
|
|
99
67
|
}
|
|
100
68
|
// When nothing is configured yet, the useful path is configuring a project right here, not
|
|
101
69
|
// typing a project ID that points at nothing. Manual entry stays available as an explicit
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
type MenuPullResult = {
|
|
3
|
+
path: 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
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
15
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
16
|
};
|
|
12
|
-
run(): Promise<
|
|
17
|
+
run(): Promise<MenuPullResult>;
|
|
13
18
|
private pullLocations;
|
|
14
19
|
private pullMenus;
|
|
15
20
|
}
|
|
21
|
+
export {};
|
|
@@ -12,6 +12,7 @@ export default class Pull extends LoopressCommand {
|
|
|
12
12
|
path: Args.string({ description: 'Path to menus directory (overrides project config)' }),
|
|
13
13
|
};
|
|
14
14
|
static description = 'Pull nav menus and the active theme menu locations from WordPress';
|
|
15
|
+
static enableJsonFlag = true;
|
|
15
16
|
static examples = ['$ lps menu pull'];
|
|
16
17
|
static flags = {
|
|
17
18
|
...LoopressCommand.dryRunFlag,
|
|
@@ -25,6 +26,7 @@ export default class Pull extends LoopressCommand {
|
|
|
25
26
|
this.log(`Menus path: ${path}`);
|
|
26
27
|
await this.pullMenus(path);
|
|
27
28
|
await this.pullLocations(path);
|
|
29
|
+
return { path, status: this.dryRun ? 'dry-run' : 'success' };
|
|
28
30
|
}
|
|
29
31
|
async pullLocations(basePath) {
|
|
30
32
|
const file = join(basePath, `${LOCATIONS_FILE_BASENAME}.json`);
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import { PushCommand } from '../../lib/push-command.js';
|
|
2
|
+
type MenuPushResult = {
|
|
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
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
15
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
16
|
};
|
|
12
|
-
run(): Promise<
|
|
17
|
+
run(): Promise<MenuPushResult>;
|
|
13
18
|
private currentRevision;
|
|
14
19
|
private pushLocations;
|
|
15
20
|
private pushMenuFile;
|
|
16
21
|
private pushMenus;
|
|
17
22
|
}
|
|
23
|
+
export {};
|
|
@@ -15,6 +15,7 @@ export default class Push extends PushCommand {
|
|
|
15
15
|
path: Args.string({ description: 'Path to menus directory (overrides project config)' }),
|
|
16
16
|
};
|
|
17
17
|
static description = 'Push local nav menus and the active theme menu locations to WordPress. Each menu\'s post_type/taxonomy items are resolved by slug on the target environment, never by a raw id; an item whose target does not exist there fails the whole menu rather than guessing.';
|
|
18
|
+
static enableJsonFlag = true;
|
|
18
19
|
static examples = ['$ lps menu push'];
|
|
19
20
|
static flags = {
|
|
20
21
|
...PushCommand.dryRunFlag,
|
|
@@ -35,9 +36,10 @@ export default class Push extends PushCommand {
|
|
|
35
36
|
this.error(`${pluralize(this.failedCount, 'menu')} failed to push.`);
|
|
36
37
|
}
|
|
37
38
|
if (this.dryRun)
|
|
38
|
-
return;
|
|
39
|
+
return { path, status: 'dry-run' };
|
|
39
40
|
await this.recordSuccess();
|
|
40
41
|
this.log('All nav menus pushed.');
|
|
42
|
+
return { path, status: 'success' };
|
|
41
43
|
}
|
|
42
44
|
async currentRevision(slug) {
|
|
43
45
|
try {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { resourceDiffCommand } from '../../lib/diff-command.js';
|
|
2
|
+
export default resourceDiffCommand('meta', {
|
|
3
|
+
description: 'Show what differs in post meta keys between your local meta/ files and a WordPress environment, or between two environments',
|
|
4
|
+
pathNoun: 'meta directory',
|
|
5
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
type RegisteredMetaKey = {
|
|
3
|
+
acf: boolean;
|
|
4
|
+
conflict: boolean;
|
|
5
|
+
key: string;
|
|
6
|
+
managed: boolean;
|
|
7
|
+
postType: string;
|
|
8
|
+
showInRest: boolean;
|
|
9
|
+
single: boolean;
|
|
10
|
+
source: 'loopress' | 'other';
|
|
11
|
+
type: string;
|
|
12
|
+
};
|
|
13
|
+
export default class MetaList extends LoopressCommand {
|
|
14
|
+
static description: string;
|
|
15
|
+
static enableJsonFlag: boolean;
|
|
16
|
+
static examples: string[];
|
|
17
|
+
run(): Promise<RegisteredMetaKey[]>;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
import { META_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
export default class MetaList extends LoopressCommand {
|
|
4
|
+
static description = 'List the meta keys registered for each post type on WordPress (with register_post_meta()), and whether Loopress declares them. ' +
|
|
5
|
+
'Only Loopress ones are pulled and pushed.';
|
|
6
|
+
static enableJsonFlag = true;
|
|
7
|
+
static examples = ['$ lps meta list'];
|
|
8
|
+
async run() {
|
|
9
|
+
const keys = await this.wp.get(META_SPEC.registeredEndpoint);
|
|
10
|
+
for (const meta of keys) {
|
|
11
|
+
const source = meta.source === 'loopress' ? 'Loopress' : 'theme or plugin';
|
|
12
|
+
const rest = meta.showInRest ? ', in REST' : '';
|
|
13
|
+
const acf = meta.acf ? ', same name as an ACF field' : '';
|
|
14
|
+
this.log(`${meta.postType}.${meta.key} (${meta.type}): ${source}${rest}${acf}`);
|
|
15
|
+
if (meta.conflict) {
|
|
16
|
+
this.warn(`${meta.postType}.${meta.key}: declared in meta/ but already registered by a theme or plugin, the Loopress version is skipped`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return keys;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { declaredPullCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { META_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
export default declaredPullCommand(META_SPEC, 'Pull the post meta keys Loopress manages on WordPress into meta/<post-type>.json files, never their values. ' +
|
|
4
|
+
'Keys registered by a theme or another plugin are never pulled, see `lps meta list`.');
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { declaredPushCommand } from '../../lib/declared-type-command.js';
|
|
2
|
+
import { META_SPEC } from '../../utils/declared-type-format.js';
|
|
3
|
+
export default declaredPushCommand(META_SPEC, 'Push local post meta keys (meta/<post-type>.json, each key mapped to its register_post_meta() arguments) to WordPress. ' +
|
|
4
|
+
'Create or update only, never deletes (use `lps meta rm`), never touches meta values.');
|