@loopress/cli 0.20.1 → 0.21.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 +128 -62
- package/dist/commands/acf/list.js +2 -1
- package/dist/commands/acf/push.js +1 -1
- package/dist/commands/api/list.d.ts +7 -4
- package/dist/commands/api/list.js +3 -10
- package/dist/commands/api/publish.js +1 -1
- package/dist/commands/api/pull.d.ts +8 -1
- package/dist/commands/api/pull.js +13 -4
- package/dist/commands/api/push.d.ts +7 -1
- package/dist/commands/api/push.js +48 -18
- package/dist/commands/composer/init.js +6 -6
- package/dist/commands/composer/pull.d.ts +7 -1
- package/dist/commands/composer/pull.js +4 -2
- package/dist/commands/composer/push.d.ts +8 -1
- package/dist/commands/composer/push.js +3 -1
- package/dist/commands/dev.d.ts +13 -0
- package/dist/commands/dev.js +97 -0
- package/dist/commands/doctor.js +4 -4
- package/dist/commands/init.js +7 -7
- package/dist/commands/login.js +3 -3
- package/dist/commands/page/list.d.ts +2 -4
- package/dist/commands/page/list.js +3 -10
- package/dist/commands/page/pull.d.ts +13 -1
- package/dist/commands/page/pull.js +5 -2
- package/dist/commands/page/push.d.ts +11 -1
- package/dist/commands/page/push.js +21 -14
- package/dist/commands/plugin/add.js +3 -3
- package/dist/commands/plugin/pull.d.ts +7 -1
- package/dist/commands/plugin/pull.js +3 -1
- package/dist/commands/plugin/push.d.ts +9 -1
- package/dist/commands/plugin/push.js +13 -9
- package/dist/commands/project/config.js +8 -8
- package/dist/commands/project/list.js +2 -2
- package/dist/commands/project/push.js +5 -5
- package/dist/commands/project/switch.js +2 -2
- package/dist/commands/snippet/list.d.ts +3 -4
- package/dist/commands/snippet/list.js +3 -10
- package/dist/commands/snippet/pull.d.ts +13 -1
- package/dist/commands/snippet/pull.js +5 -2
- package/dist/commands/snippet/push.d.ts +11 -1
- package/dist/commands/snippet/push.js +15 -5
- package/dist/commands/status.d.ts +13 -1
- package/dist/commands/status.js +25 -18
- package/dist/config/auth.manager.d.ts +2 -2
- package/dist/config/project-config.manager.d.ts +1 -1
- package/dist/config/project-config.manager.js +5 -6
- package/dist/lib/base.d.ts +2 -2
- package/dist/lib/base.js +3 -2
- package/dist/lib/bootstrap-full-install.d.ts +1 -1
- package/dist/lib/dev-watch.d.ts +16 -0
- package/dist/lib/dev-watch.js +71 -0
- package/dist/lib/find-orphaned-files.d.ts +3 -2
- package/dist/lib/find-orphaned-files.js +40 -4
- package/dist/lib/github-release.js +1 -1
- package/dist/lib/interactive.js +1 -1
- package/dist/lib/load-files.d.ts +1 -0
- package/dist/lib/load-files.js +40 -11
- package/dist/lib/load-snippets.d.ts +1 -1
- package/dist/lib/load-snippets.js +11 -9
- package/dist/lib/local-callback-server.js +8 -6
- package/dist/lib/plugin-detection.d.ts +1 -1
- package/dist/lib/push-command.js +2 -2
- package/dist/lib/rotate-app-password.d.ts +1 -1
- package/dist/lib/rotate-app-password.js +1 -1
- package/dist/lib/temp-admin.d.ts +3 -3
- package/dist/lib/temp-admin.js +2 -2
- package/dist/lib/wp-authorize-flow.js +5 -1
- package/dist/lib/wp-client.d.ts +2 -2
- package/dist/lib/wp-client.js +7 -0
- package/dist/types/plugin.d.ts +4 -4
- package/dist/types/snippet.d.ts +3 -3
- package/dist/utils/composer.d.ts +2 -2
- package/dist/utils/form-format.js +1 -1
- package/dist/utils/loopress-config.d.ts +1 -1
- package/dist/utils/page-format.js +2 -2
- package/dist/utils/plugins.d.ts +5 -5
- package/dist/utils/plugins.js +3 -3
- package/dist/utils/seo-format.d.ts +6 -6
- package/dist/utils/snippet-format.d.ts +2 -2
- package/dist/utils/snippet-format.js +10 -5
- package/oclif.manifest.json +153 -31
- package/package.json +9 -8
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
type PulledPage = {
|
|
3
|
+
id: number;
|
|
4
|
+
title: string;
|
|
5
|
+
};
|
|
6
|
+
type PullResult = {
|
|
7
|
+
orphans: string[];
|
|
8
|
+
pulled: PulledPage[];
|
|
9
|
+
skipped: number;
|
|
10
|
+
status: 'dry-run' | 'success';
|
|
11
|
+
};
|
|
2
12
|
export default class Pull extends LoopressCommand {
|
|
3
13
|
static args: {
|
|
4
14
|
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
15
|
};
|
|
6
16
|
static description: string;
|
|
17
|
+
static enableJsonFlag: boolean;
|
|
7
18
|
static examples: string[];
|
|
8
19
|
static flags: {
|
|
9
20
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
21
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
22
|
};
|
|
12
|
-
run(): Promise<
|
|
23
|
+
run(): Promise<PullResult>;
|
|
13
24
|
}
|
|
25
|
+
export {};
|
|
@@ -10,6 +10,7 @@ export default class Pull extends LoopressCommand {
|
|
|
10
10
|
path: Args.string({ description: 'Path to pages directory (overrides project config)' }),
|
|
11
11
|
};
|
|
12
12
|
static description = 'Pull pages from WordPress';
|
|
13
|
+
static enableJsonFlag = true;
|
|
13
14
|
static examples = ['$ lps page pull'];
|
|
14
15
|
static flags = {
|
|
15
16
|
...LoopressCommand.dryRunFlag,
|
|
@@ -32,12 +33,13 @@ export default class Pull extends LoopressCommand {
|
|
|
32
33
|
extensions: ['.json', '.html'],
|
|
33
34
|
key: numericPrefixKey,
|
|
34
35
|
});
|
|
36
|
+
const pulled = withId.map((page) => ({ id: getPageId(page), title: getPageTitle(page) }));
|
|
35
37
|
if (this.dryRun) {
|
|
36
38
|
this.log(`[dry-run] Would pull ${withId.length} page${withId.length === 1 ? '' : 's'} to ${path}`);
|
|
37
39
|
if (orphans.length > 0) {
|
|
38
40
|
this.log(`[dry-run] Would remove ${orphans.length} local file${orphans.length === 1 ? '' : 's'} in ${path} no longer present on WordPress: ${orphans.join(', ')}`);
|
|
39
41
|
}
|
|
40
|
-
return;
|
|
42
|
+
return { orphans, pulled, skipped, status: 'dry-run' };
|
|
41
43
|
}
|
|
42
44
|
if (withId.length > 0)
|
|
43
45
|
await mkdir(path, { recursive: true });
|
|
@@ -55,11 +57,12 @@ export default class Pull extends LoopressCommand {
|
|
|
55
57
|
},
|
|
56
58
|
title: `Pull ${title}`,
|
|
57
59
|
};
|
|
58
|
-
})).run();
|
|
60
|
+
}), { renderer: this.jsonEnabled() ? 'silent' : 'default' }).run();
|
|
59
61
|
await this.removeOrphanedFiles(path, orphans, `in ${path} no longer present on WordPress`);
|
|
60
62
|
this.log(`Pulled ${withId.length} page${withId.length === 1 ? '' : 's'} to ${path}`);
|
|
61
63
|
if (skipped > 0) {
|
|
62
64
|
this.warn(`${skipped} page${skipped === 1 ? '' : 's'} skipped because they have no id`);
|
|
63
65
|
}
|
|
66
|
+
return { orphans, pulled, skipped, status: 'success' };
|
|
64
67
|
}
|
|
65
68
|
}
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import { PushCommand } from '../../lib/push-command.js';
|
|
2
|
+
type PushedPage = {
|
|
3
|
+
id: null | number;
|
|
4
|
+
title: string;
|
|
5
|
+
};
|
|
6
|
+
type PushResult = {
|
|
7
|
+
pushed: PushedPage[];
|
|
8
|
+
status: 'dry-run' | 'success';
|
|
9
|
+
};
|
|
2
10
|
export default class Push extends PushCommand {
|
|
3
11
|
static args: {
|
|
4
12
|
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
13
|
};
|
|
6
14
|
static description: string;
|
|
15
|
+
static enableJsonFlag: boolean;
|
|
7
16
|
static examples: string[];
|
|
8
17
|
static flags: {
|
|
9
18
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
19
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
20
|
};
|
|
12
|
-
run(): Promise<
|
|
21
|
+
run(): Promise<PushResult>;
|
|
13
22
|
private ensureCanonicalFilename;
|
|
14
23
|
private loadFiles;
|
|
15
24
|
private pushPage;
|
|
16
25
|
}
|
|
26
|
+
export {};
|
|
@@ -10,6 +10,7 @@ export default class Push extends PushCommand {
|
|
|
10
10
|
path: Args.string({ description: 'Path to pages directory (overrides project config)' }),
|
|
11
11
|
};
|
|
12
12
|
static description = 'Push pages to WordPress. Local files created or updated remotely are renamed on disk to the `<id>-<slug>` convention.';
|
|
13
|
+
static enableJsonFlag = true;
|
|
13
14
|
static examples = ['$ lps page push'];
|
|
14
15
|
static flags = {
|
|
15
16
|
...PushCommand.dryRunFlag,
|
|
@@ -23,17 +24,22 @@ export default class Push extends PushCommand {
|
|
|
23
24
|
this.log(`Pages path: ${path}`);
|
|
24
25
|
const pages = await this.loadFiles(path);
|
|
25
26
|
this.log(`Found ${pages.length} page${pages.length === 1 ? '' : 's'} to push`);
|
|
27
|
+
const pushed = [];
|
|
26
28
|
await new Listr(pages.map((page) => ({
|
|
27
|
-
task: async (_ctx, task) =>
|
|
29
|
+
task: async (_ctx, task) => {
|
|
30
|
+
const id = await this.pushPage(page, task);
|
|
31
|
+
pushed.push({ id, title: getPageTitle(page.meta) });
|
|
32
|
+
},
|
|
28
33
|
title: `Push ${getPageTitle(page.meta)}`,
|
|
29
|
-
})), { concurrent: false, exitOnError: false }).run();
|
|
34
|
+
})), { concurrent: false, exitOnError: false, renderer: this.jsonEnabled() ? 'silent' : 'default' }).run();
|
|
30
35
|
if (this.failedCount > 0) {
|
|
31
36
|
this.error(`${this.failedCount} page${this.failedCount === 1 ? '' : 's'} failed to push.`);
|
|
32
37
|
}
|
|
33
38
|
if (this.dryRun)
|
|
34
|
-
return;
|
|
39
|
+
return { pushed, status: 'dry-run' };
|
|
35
40
|
await this.recordSuccess();
|
|
36
41
|
this.log('All pages pushed.');
|
|
42
|
+
return { pushed, status: 'success' };
|
|
37
43
|
}
|
|
38
44
|
// Renames the local file pair to the `<id>-<slug>` convention used by `page pull` whenever it
|
|
39
45
|
// doesn't already match, same principle as ensureCanonicalFilename in commands/snippet/push.ts:
|
|
@@ -108,16 +114,16 @@ export default class Push extends PushCommand {
|
|
|
108
114
|
if (this.dryRun) {
|
|
109
115
|
if (task)
|
|
110
116
|
task.output = `[dry-run] Would push: ${title}`;
|
|
111
|
-
return;
|
|
117
|
+
return getPageId(page.meta);
|
|
112
118
|
}
|
|
113
119
|
try {
|
|
114
|
-
|
|
120
|
+
let id = getPageId(page.meta);
|
|
115
121
|
const payload = { ...page.meta, content: page.content };
|
|
116
122
|
if (id === null) {
|
|
117
123
|
const created = await this.wp.post(PAGE_ENDPOINT, payload);
|
|
118
|
-
|
|
119
|
-
if (
|
|
120
|
-
await this.ensureCanonicalFilename(page,
|
|
124
|
+
id = getPageId(created);
|
|
125
|
+
if (id !== null)
|
|
126
|
+
await this.ensureCanonicalFilename(page, id, title);
|
|
121
127
|
}
|
|
122
128
|
else {
|
|
123
129
|
try {
|
|
@@ -132,16 +138,17 @@ export default class Push extends PushCommand {
|
|
|
132
138
|
// be stripped before falling back to create.
|
|
133
139
|
if (!isNotFoundError(error))
|
|
134
140
|
throw error;
|
|
135
|
-
const
|
|
136
|
-
delete
|
|
137
|
-
const created = await this.wp.post(PAGE_ENDPOINT,
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
await this.ensureCanonicalFilename(page,
|
|
141
|
+
const newPayload = { ...payload };
|
|
142
|
+
delete newPayload.id;
|
|
143
|
+
const created = await this.wp.post(PAGE_ENDPOINT, newPayload);
|
|
144
|
+
id = getPageId(created);
|
|
145
|
+
if (id !== null)
|
|
146
|
+
await this.ensureCanonicalFilename(page, id, title);
|
|
141
147
|
}
|
|
142
148
|
}
|
|
143
149
|
if (task)
|
|
144
150
|
task.output = `Pushed: ${title}`;
|
|
151
|
+
return id;
|
|
145
152
|
}
|
|
146
153
|
catch (error) {
|
|
147
154
|
this.reportTaskFailure(`Failed to push ${title}: ${error.message}`, error, task);
|
|
@@ -18,15 +18,15 @@ export default class Add extends LoopressCommand {
|
|
|
18
18
|
this.log(`${slug} is already in loopress.json, nothing to do.`);
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
|
-
const
|
|
21
|
+
const isUpdated = existing[slug] !== undefined;
|
|
22
22
|
if (this.dryRun) {
|
|
23
|
-
this.log(`[dry-run] Would ${
|
|
23
|
+
this.log(`[dry-run] Would ${isUpdated ? 'update' : 'add'} ${slug} in loopress.json`);
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
await writeLocalConfig({
|
|
27
27
|
...this.localConfig,
|
|
28
28
|
plugins: { ...existing, [slug]: 'latest' },
|
|
29
29
|
});
|
|
30
|
-
this.log(`${
|
|
30
|
+
this.log(`${isUpdated ? 'Updated' : 'Added'} ${slug}`);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
import { type MergeResult } from '../../utils/plugins.js';
|
|
3
|
+
type PullResult = MergeResult & {
|
|
4
|
+
status: 'dry-run' | 'success';
|
|
5
|
+
};
|
|
2
6
|
export default class Pull extends LoopressCommand {
|
|
3
7
|
static description: string;
|
|
8
|
+
static enableJsonFlag: boolean;
|
|
4
9
|
static examples: string[];
|
|
5
10
|
static flags: {
|
|
6
11
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
12
|
};
|
|
8
|
-
run(): Promise<
|
|
13
|
+
run(): Promise<PullResult>;
|
|
9
14
|
}
|
|
15
|
+
export {};
|
|
@@ -4,6 +4,7 @@ import { writeLocalConfig } from '../../utils/loopress-config.js';
|
|
|
4
4
|
import { mergePluginManifest, parseInstalledPlugins } from '../../utils/plugins.js';
|
|
5
5
|
export default class Pull extends LoopressCommand {
|
|
6
6
|
static description = 'Pull installed plugins from WordPress into loopress.json';
|
|
7
|
+
static enableJsonFlag = true;
|
|
7
8
|
static examples = ['$ lps plugin pull', '$ lps plugin pull --dry-run'];
|
|
8
9
|
static flags = {
|
|
9
10
|
...LoopressCommand.dryRunFlag,
|
|
@@ -31,7 +32,7 @@ export default class Pull extends LoopressCommand {
|
|
|
31
32
|
for (const u of updated)
|
|
32
33
|
this.log(` ~ ${u.slug} (${u.from} → ${u.to})`);
|
|
33
34
|
}
|
|
34
|
-
return;
|
|
35
|
+
return { added, merged, status: 'dry-run', updated };
|
|
35
36
|
}
|
|
36
37
|
await writeLocalConfig({ ...this.localConfig, plugins: merged });
|
|
37
38
|
this.log(`Wrote ${Object.keys(merged).length} plugins to loopress.json`);
|
|
@@ -39,5 +40,6 @@ export default class Pull extends LoopressCommand {
|
|
|
39
40
|
this.log(` + Added: ${added.join(', ')}`);
|
|
40
41
|
for (const u of updated)
|
|
41
42
|
this.log(` ~ Updated: ${u.slug} ${u.from} → ${u.to}`);
|
|
43
|
+
return { added, merged, status: 'success', updated };
|
|
42
44
|
}
|
|
43
45
|
}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { PushCommand } from '../../lib/push-command.js';
|
|
2
|
+
type PushResult = {
|
|
3
|
+
activated: string[];
|
|
4
|
+
installed: string[];
|
|
5
|
+
skippedComposerManaged: string[];
|
|
6
|
+
status: 'dry-run' | 'in-sync' | 'success';
|
|
7
|
+
};
|
|
2
8
|
export default class Push extends PushCommand {
|
|
3
9
|
static description: string;
|
|
10
|
+
static enableJsonFlag: boolean;
|
|
4
11
|
static examples: string[];
|
|
5
12
|
static flags: {
|
|
6
13
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
14
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
15
|
};
|
|
9
|
-
run(): Promise<
|
|
16
|
+
run(): Promise<PushResult>;
|
|
10
17
|
private activatePlugin;
|
|
11
18
|
private installPlugin;
|
|
12
19
|
private performPluginAction;
|
|
13
20
|
}
|
|
21
|
+
export {};
|
|
@@ -4,6 +4,7 @@ import { getComposerManagedSlugs, readComposerJson } from '../../utils/composer.
|
|
|
4
4
|
import { diffPlugins, parseInstalledPlugins } from '../../utils/plugins.js';
|
|
5
5
|
export default class Push extends PushCommand {
|
|
6
6
|
static description = 'Push plugins to WordPress to match loopress.json';
|
|
7
|
+
static enableJsonFlag = true;
|
|
7
8
|
static examples = ['$ lps plugin push', '$ lps plugin push --dry-run'];
|
|
8
9
|
static flags = {
|
|
9
10
|
...PushCommand.dryRunFlag,
|
|
@@ -18,9 +19,9 @@ export default class Push extends PushCommand {
|
|
|
18
19
|
const composerJson = await readComposerJson();
|
|
19
20
|
const composerSlugs = composerJson ? getComposerManagedSlugs(composerJson) : [];
|
|
20
21
|
const filteredManifest = Object.fromEntries(Object.entries(manifest).filter(([slug]) => !composerSlugs.includes(slug)));
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
23
|
-
this.log(`Skipping ${
|
|
22
|
+
const skippedComposerManaged = composerSlugs.filter((slug) => Object.hasOwn(manifest, slug));
|
|
23
|
+
if (skippedComposerManaged.length > 0) {
|
|
24
|
+
this.log(`Skipping ${skippedComposerManaged.length} Composer-managed ${skippedComposerManaged.length === 1 ? 'plugin' : 'plugins'}: ${skippedComposerManaged.join(', ')}`);
|
|
24
25
|
this.log('Run `lps composer push` to deploy them.');
|
|
25
26
|
}
|
|
26
27
|
this.log(`Pushing plugins to ${url}`);
|
|
@@ -29,7 +30,7 @@ export default class Push extends PushCommand {
|
|
|
29
30
|
const { toActivate, toInstall } = diffPlugins(filteredManifest, installed);
|
|
30
31
|
if (toInstall.length === 0 && toActivate.length === 0) {
|
|
31
32
|
this.log('Everything is already in sync.');
|
|
32
|
-
return;
|
|
33
|
+
return { activated: [], installed: [], skippedComposerManaged, status: 'in-sync' };
|
|
33
34
|
}
|
|
34
35
|
if (toInstall.length > 0) {
|
|
35
36
|
this.log(`\nTo install (${toInstall.length}):`);
|
|
@@ -41,32 +42,35 @@ export default class Push extends PushCommand {
|
|
|
41
42
|
for (const a of toActivate)
|
|
42
43
|
this.log(` ↑ ${a.slug}`);
|
|
43
44
|
}
|
|
45
|
+
const installedSlugs = toInstall.map((a) => a.slug);
|
|
46
|
+
const activatedSlugs = toActivate.map((a) => a.slug);
|
|
44
47
|
if (this.dryRun)
|
|
45
|
-
return;
|
|
48
|
+
return { activated: activatedSlugs, installed: installedSlugs, skippedComposerManaged, status: 'dry-run' };
|
|
46
49
|
// Installing with `status: active` activates in the same call, so installs never need a
|
|
47
50
|
// separate activation step the way the old custom endpoint's two-step flow did.
|
|
48
51
|
if (toInstall.length > 0) {
|
|
49
52
|
await new Listr(toInstall.map((action) => ({
|
|
50
53
|
task: async (_ctx, task) => this.installPlugin(action.slug, task),
|
|
51
54
|
title: `Install ${action.slug}`,
|
|
52
|
-
})), { concurrent: false, exitOnError: false }).run();
|
|
55
|
+
})), { concurrent: false, exitOnError: false, renderer: this.jsonEnabled() ? 'silent' : 'default' }).run();
|
|
53
56
|
}
|
|
54
57
|
if (toActivate.length > 0) {
|
|
55
58
|
await new Listr(toActivate.map((action) => ({
|
|
56
59
|
task: async (_ctx, task) => this.activatePlugin(action.file, action.slug, task),
|
|
57
60
|
title: `Activate ${action.slug}`,
|
|
58
|
-
})), { concurrent: false, exitOnError: false }).run();
|
|
61
|
+
})), { concurrent: false, exitOnError: false, renderer: this.jsonEnabled() ? 'silent' : 'default' }).run();
|
|
59
62
|
}
|
|
60
63
|
if (this.failedCount > 0) {
|
|
61
64
|
this.error(`${this.failedCount} plugin${this.failedCount === 1 ? '' : 's'} failed to install or activate.`);
|
|
62
65
|
}
|
|
63
66
|
await this.recordSuccess();
|
|
67
|
+
return { activated: activatedSlugs, installed: installedSlugs, skippedComposerManaged, status: 'success' };
|
|
64
68
|
}
|
|
65
69
|
async activatePlugin(file, slug, task) {
|
|
66
|
-
await this.performPluginAction('activate', slug, () => this.wp.put(`wp/v2/plugins/${file}`, { status: 'active' }), task);
|
|
70
|
+
await this.performPluginAction('activate', slug, async () => this.wp.put(`wp/v2/plugins/${file}`, { status: 'active' }), task);
|
|
67
71
|
}
|
|
68
72
|
async installPlugin(slug, task) {
|
|
69
|
-
await this.performPluginAction('install', slug, () => this.wp.post('wp/v2/plugins', { slug, status: 'active' }), task);
|
|
73
|
+
await this.performPluginAction('install', slug, async () => this.wp.post('wp/v2/plugins', { slug, status: 'active' }), task);
|
|
70
74
|
}
|
|
71
75
|
async performPluginAction(verb, slug, request, task) {
|
|
72
76
|
try {
|
|
@@ -36,11 +36,11 @@ export default class Config extends Command {
|
|
|
36
36
|
: envChoice;
|
|
37
37
|
const existingEnv = configManager.getEnvironment(projectId, envName);
|
|
38
38
|
if (existingEnv) {
|
|
39
|
-
const
|
|
39
|
+
const isOverwrite = await confirm({
|
|
40
40
|
default: false,
|
|
41
41
|
message: `"${projectName}/${envName}" already exists. Overwrite?`,
|
|
42
42
|
});
|
|
43
|
-
if (!
|
|
43
|
+
if (!isOverwrite) {
|
|
44
44
|
this.log('Aborted.');
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
@@ -92,17 +92,17 @@ export default class Config extends Command {
|
|
|
92
92
|
// A detection failure (network hiccup, plugins REST disabled) is treated as "assume it's
|
|
93
93
|
// there" rather than surprising the user with an account-creating prompt they didn't ask
|
|
94
94
|
// for; `lps project config` can simply be re-run if it genuinely isn't installed.
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
95
|
+
const isAlreadyActive = await isLoopressFullActive(wp).catch(() => true);
|
|
96
|
+
if (isAlreadyActive)
|
|
97
97
|
return;
|
|
98
|
-
const
|
|
98
|
+
const isProceed = await confirm({
|
|
99
99
|
default: true,
|
|
100
100
|
message: 'Loopress Full was not detected on this site. Install it now? (creates a temporary admin account, removed automatically afterward)',
|
|
101
101
|
});
|
|
102
|
-
if (!
|
|
102
|
+
if (!isProceed)
|
|
103
103
|
return;
|
|
104
104
|
try {
|
|
105
|
-
await bootstrapLoopressFull(wp, url, (message) => this.log(message));
|
|
105
|
+
await bootstrapLoopressFull(wp, url, (message) => { this.log(message); });
|
|
106
106
|
}
|
|
107
107
|
catch (error) {
|
|
108
108
|
this.warn(error.message);
|
|
@@ -137,7 +137,7 @@ export default class Config extends Command {
|
|
|
137
137
|
return this.promptManualCredentials();
|
|
138
138
|
}
|
|
139
139
|
try {
|
|
140
|
-
const { password, userLogin } = await authorizeWithBrowser(url, (message) => this.log(message));
|
|
140
|
+
const { password, userLogin } = await authorizeWithBrowser(url, (message) => { this.log(message); });
|
|
141
141
|
return { appPassword: password, user: userLogin };
|
|
142
142
|
}
|
|
143
143
|
catch (error) {
|
|
@@ -18,8 +18,8 @@ export default class List extends Command {
|
|
|
18
18
|
const currentTag = project.isCurrent ? ` ${c('green', '[current]')}` : '';
|
|
19
19
|
this.log(`${marker} ${name}${currentTag}`);
|
|
20
20
|
for (const env of envs) {
|
|
21
|
-
const envMarker = env.isCurrent ?
|
|
22
|
-
const envName = env.isCurrent ?
|
|
21
|
+
const envMarker = c(env.isCurrent ? 'cyan' : 'dim', '·');
|
|
22
|
+
const envName = c(env.isCurrent ? 'cyan' : 'dim', env.name.padEnd(15));
|
|
23
23
|
const envUrl = c('dim', env.url);
|
|
24
24
|
const activeTag = env.isCurrent ? ` ${c('cyan', '←')}` : '';
|
|
25
25
|
this.log(` ${envMarker} ${envName} ${envUrl}${activeTag}`);
|
|
@@ -16,7 +16,7 @@ export default class Push extends Command {
|
|
|
16
16
|
yes = false;
|
|
17
17
|
async run() {
|
|
18
18
|
const { flags } = await this.parse(Push);
|
|
19
|
-
this.yes =
|
|
19
|
+
this.yes = flags.yes;
|
|
20
20
|
const token = authManager.getAuth()?.token;
|
|
21
21
|
if (!token) {
|
|
22
22
|
this.error('Not logged in. Run `lps login` first.');
|
|
@@ -158,8 +158,8 @@ export default class Push extends Command {
|
|
|
158
158
|
}
|
|
159
159
|
const match = apiProject?.environments.find((candidate) => candidate.name === env.name && !claimedEnvironmentIds.has(candidate.id));
|
|
160
160
|
if (match) {
|
|
161
|
-
const
|
|
162
|
-
if (
|
|
161
|
+
const isLink = await this.confirmLink(`Environment "${env.name}" already exists on "${apiProject?.name}". Link to it instead of creating a new one?`);
|
|
162
|
+
if (isLink) {
|
|
163
163
|
claimedEnvironmentIds.add(match.id);
|
|
164
164
|
return { action: 'link', apiEnvironmentId: match.id, env, projectId };
|
|
165
165
|
}
|
|
@@ -174,8 +174,8 @@ export default class Push extends Command {
|
|
|
174
174
|
const slug = toSlug(project.name);
|
|
175
175
|
const match = apiProjects.find((candidate) => candidate.slug === slug && !claimedProjectIds.has(candidate.id));
|
|
176
176
|
if (match) {
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
177
|
+
const isLink = await this.confirmLink(`A project named "${project.name}" already exists on your account. Link to it instead of creating a new one?`);
|
|
178
|
+
if (isLink) {
|
|
179
179
|
claimedProjectIds.add(match.id);
|
|
180
180
|
return { action: 'link', apiProjectId: match.id, project };
|
|
181
181
|
}
|
|
@@ -28,7 +28,7 @@ export default class Switch extends Command {
|
|
|
28
28
|
return { envName: env.name, projectId: project.id, projectName: project.name };
|
|
29
29
|
}
|
|
30
30
|
const choices = groups.flatMap(({ envs, project }) => [
|
|
31
|
-
new Separator(project.isCurrent ?
|
|
31
|
+
new Separator(c(project.isCurrent ? 'green' : 'dim', `─── ${project.name} ───`)),
|
|
32
32
|
...envs.map((env) => ({
|
|
33
33
|
name: `${env.name.padEnd(20)} ${env.url}${env.isCurrent ? ' [current]' : ''}`,
|
|
34
34
|
value: `${project.id}::${env.name}`,
|
|
@@ -40,7 +40,7 @@ export default class Switch extends Command {
|
|
|
40
40
|
default: current && `${current.project.id}::${current.env.name}`,
|
|
41
41
|
message: 'Select project / environment',
|
|
42
42
|
});
|
|
43
|
-
const [projectId, envName] = chosen.split('::');
|
|
43
|
+
const [projectId, envName] = chosen.split('::', 2);
|
|
44
44
|
const { project } = groups.find((group) => group.project.id === projectId);
|
|
45
45
|
return { envName, projectId, projectName: project.name };
|
|
46
46
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
import { type NormalizedSnippet } from '../../utils/snippet-format.js';
|
|
2
3
|
export default class List extends LoopressCommand {
|
|
3
4
|
static description: string;
|
|
5
|
+
static enableJsonFlag: boolean;
|
|
4
6
|
static examples: string[];
|
|
5
|
-
|
|
6
|
-
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
-
};
|
|
8
|
-
run(): Promise<void>;
|
|
7
|
+
run(): Promise<NormalizedSnippet[]>;
|
|
9
8
|
}
|
|
@@ -1,23 +1,15 @@
|
|
|
1
|
-
import { Flags } from '@oclif/core';
|
|
2
1
|
import { LoopressCommand } from '../../lib/base.js';
|
|
3
2
|
import { normalizeSnippet, SNIPPETS_ENDPOINT } from '../../utils/snippet-format.js';
|
|
4
3
|
export default class List extends LoopressCommand {
|
|
5
4
|
static description = 'List snippets from WordPress';
|
|
5
|
+
static enableJsonFlag = true;
|
|
6
6
|
static examples = ['$ lps snippet list'];
|
|
7
|
-
static flags = {
|
|
8
|
-
json: Flags.boolean({ char: 'j', description: 'Output in JSON format' }),
|
|
9
|
-
};
|
|
10
7
|
async run() {
|
|
11
|
-
const { flags } = await this.parse(List);
|
|
12
8
|
const remoteList = await this.wp.get(SNIPPETS_ENDPOINT);
|
|
13
9
|
const snippets = remoteList.map((r) => normalizeSnippet(r));
|
|
14
|
-
if (flags.json) {
|
|
15
|
-
this.log(JSON.stringify(snippets, null, 2));
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
10
|
if (snippets.length === 0) {
|
|
19
11
|
this.log('No snippets found');
|
|
20
|
-
return;
|
|
12
|
+
return snippets;
|
|
21
13
|
}
|
|
22
14
|
this.log(`Found ${snippets.length} snippet${snippets.length === 1 ? '' : 's'}:`);
|
|
23
15
|
this.log('');
|
|
@@ -32,5 +24,6 @@ export default class List extends LoopressCommand {
|
|
|
32
24
|
}
|
|
33
25
|
this.log('');
|
|
34
26
|
}
|
|
27
|
+
return snippets;
|
|
35
28
|
}
|
|
36
29
|
}
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import { LoopressCommand } from '../../lib/base.js';
|
|
2
|
+
type PulledSnippet = {
|
|
3
|
+
id: number;
|
|
4
|
+
name: string;
|
|
5
|
+
};
|
|
6
|
+
type PullResult = {
|
|
7
|
+
orphans: string[];
|
|
8
|
+
pulled: PulledSnippet[];
|
|
9
|
+
skipped: number;
|
|
10
|
+
status: 'dry-run' | 'success';
|
|
11
|
+
};
|
|
2
12
|
export default class Pull extends LoopressCommand {
|
|
3
13
|
static args: {
|
|
4
14
|
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
15
|
};
|
|
6
16
|
static description: string;
|
|
17
|
+
static enableJsonFlag: boolean;
|
|
7
18
|
static examples: string[];
|
|
8
19
|
static flags: {
|
|
9
20
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
21
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
22
|
};
|
|
12
|
-
run(): Promise<
|
|
23
|
+
run(): Promise<PullResult>;
|
|
13
24
|
}
|
|
25
|
+
export {};
|
|
@@ -18,6 +18,7 @@ export default class Pull extends LoopressCommand {
|
|
|
18
18
|
path: Args.string({ description: 'Path to snippets directory (overrides project config)' }),
|
|
19
19
|
};
|
|
20
20
|
static description = 'Pull snippets from WordPress';
|
|
21
|
+
static enableJsonFlag = true;
|
|
21
22
|
static examples = ['$ lps snippet pull', '$ lps snippet pull --path ./snippets'];
|
|
22
23
|
static flags = {
|
|
23
24
|
...LoopressCommand.dryRunFlag,
|
|
@@ -40,12 +41,13 @@ export default class Pull extends LoopressCommand {
|
|
|
40
41
|
extensions: ['.json', ...Object.values(EXTENSIONS).map((ext) => `.${ext}`)],
|
|
41
42
|
key: numericPrefixKey,
|
|
42
43
|
});
|
|
44
|
+
const pulled = pullable.map((snippet) => ({ id: snippet.id, name: snippet.name }));
|
|
43
45
|
if (this.dryRun) {
|
|
44
46
|
this.log(`[dry-run] Would pull ${snippets.length} snippet${snippets.length === 1 ? '' : 's'} to ${path}`);
|
|
45
47
|
if (orphans.length > 0) {
|
|
46
48
|
this.log(`[dry-run] Would remove ${orphans.length} local file${orphans.length === 1 ? '' : 's'} whose snippet no longer exists on WordPress: ${orphans.join(', ')}`);
|
|
47
49
|
}
|
|
48
|
-
return;
|
|
50
|
+
return { orphans, pulled, skipped, status: 'dry-run' };
|
|
49
51
|
}
|
|
50
52
|
await mkdir(path, { recursive: true });
|
|
51
53
|
await new Listr(pullable.map((snippet) => ({
|
|
@@ -57,11 +59,12 @@ export default class Pull extends LoopressCommand {
|
|
|
57
59
|
task.output = `Pulled: ${snippet.name}`;
|
|
58
60
|
},
|
|
59
61
|
title: `Pull ${snippet.name}`,
|
|
60
|
-
}))).run();
|
|
62
|
+
})), { renderer: this.jsonEnabled() ? 'silent' : 'default' }).run();
|
|
61
63
|
await this.removeOrphanedFiles(path, orphans, 'whose snippet no longer exists on WordPress');
|
|
62
64
|
this.log(`Pulled ${pullable.length} snippet${pullable.length === 1 ? '' : 's'} to ${path}`);
|
|
63
65
|
if (skipped > 0) {
|
|
64
66
|
this.warn(`${skipped} snippet${skipped === 1 ? '' : 's'} skipped because they have no name`);
|
|
65
67
|
}
|
|
68
|
+
return { orphans, pulled, skipped, status: 'success' };
|
|
66
69
|
}
|
|
67
70
|
}
|
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import { PushCommand } from '../../lib/push-command.js';
|
|
2
|
+
type PushedSnippet = {
|
|
3
|
+
id?: number;
|
|
4
|
+
name: string;
|
|
5
|
+
};
|
|
6
|
+
type PushResult = {
|
|
7
|
+
pushed: PushedSnippet[];
|
|
8
|
+
status: 'dry-run' | 'success';
|
|
9
|
+
};
|
|
2
10
|
export default class Push extends PushCommand {
|
|
3
11
|
static args: {
|
|
4
12
|
path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
13
|
};
|
|
6
14
|
static description: string;
|
|
15
|
+
static enableJsonFlag: boolean;
|
|
7
16
|
static examples: string[];
|
|
8
17
|
static flags: {
|
|
9
18
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
19
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
20
|
};
|
|
12
|
-
run(): Promise<
|
|
21
|
+
run(): Promise<PushResult>;
|
|
13
22
|
private ensureCanonicalFilename;
|
|
14
23
|
private loadSnippets;
|
|
15
24
|
private pushSnippet;
|
|
16
25
|
private toPayload;
|
|
17
26
|
}
|
|
27
|
+
export {};
|