@loopress/cli 0.6.0 → 0.8.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 +98 -117
- package/dist/commands/composer/pull.js +5 -15
- package/dist/commands/composer/push.js +8 -17
- package/dist/commands/init.js +15 -6
- package/dist/commands/login.js +2 -2
- package/dist/commands/logout.js +2 -2
- package/dist/commands/plugin/add.d.ts +0 -2
- package/dist/commands/plugin/add.js +11 -32
- package/dist/commands/plugin/pull.js +8 -19
- package/dist/commands/plugin/push.d.ts +1 -0
- package/dist/commands/plugin/push.js +23 -48
- package/dist/commands/project/config.d.ts +1 -0
- package/dist/commands/project/config.js +36 -17
- package/dist/commands/project/list.js +4 -5
- package/dist/commands/project/remove.js +33 -14
- package/dist/commands/project/switch.d.ts +2 -0
- package/dist/commands/project/switch.js +28 -9
- package/dist/commands/snippet/list.d.ts +1 -1
- package/dist/commands/snippet/list.js +26 -41
- package/dist/commands/snippet/pull.d.ts +1 -1
- package/dist/commands/snippet/pull.js +45 -53
- package/dist/commands/snippet/push.d.ts +2 -2
- package/dist/commands/snippet/push.js +112 -68
- package/dist/commands/{project/remove-env.d.ts → status.d.ts} +3 -1
- package/dist/commands/status.js +66 -0
- package/dist/config/auth.manager.d.ts +0 -2
- package/dist/config/auth.manager.js +5 -25
- package/dist/config/json-file.d.ts +2 -0
- package/dist/config/json-file.js +21 -0
- package/dist/config/project-config.manager.d.ts +18 -13
- package/dist/config/project-config.manager.js +90 -59
- package/dist/lib/base.d.ts +14 -4
- package/dist/lib/base.js +69 -29
- package/dist/lib/push-command.d.ts +0 -1
- package/dist/lib/push-command.js +0 -1
- package/dist/lib/wp-client.d.ts +15 -0
- package/dist/lib/wp-client.js +53 -0
- package/dist/{config/types.d.ts → types/config.d.ts} +5 -2
- package/dist/types/snippet.d.ts +8 -0
- package/dist/utils/loopress-config.js +5 -2
- package/dist/utils/snippet-plugin-flag.d.ts +3 -0
- package/dist/utils/snippet-plugin-flag.js +8 -0
- package/dist/utils/snippet-plugin.d.ts +24 -2
- package/dist/utils/snippet-plugin.js +170 -14
- package/oclif.manifest.json +122 -108
- package/package.json +21 -4
- package/dist/commands/project/remove-env.js +0 -33
- package/dist/commands/project/switch-env.d.ts +0 -6
- package/dist/commands/project/switch-env.js +0 -33
- package/dist/types/menu.d.ts +0 -7
- package/dist/types/menu.js +0 -1
- /package/dist/{config/types.js → types/config.js} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Args
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
2
|
import got from 'got';
|
|
3
|
-
import { spawnSync } from 'node:child_process';
|
|
4
3
|
import { LoopressCommand } from '../../lib/base.js';
|
|
5
|
-
import {
|
|
4
|
+
import { writeLocalConfig } from '../../utils/loopress-config.js';
|
|
6
5
|
const WP_ORG_API = 'https://api.wordpress.org/plugins/info/1.2/';
|
|
6
|
+
const WP_ORG_TIMEOUT_MS = 10_000;
|
|
7
7
|
export async function resolvePluginVersion(slug, version) {
|
|
8
8
|
if (version !== 'latest')
|
|
9
9
|
return version;
|
|
@@ -15,6 +15,7 @@ export async function resolvePluginVersion(slug, version) {
|
|
|
15
15
|
action: 'plugin_information',
|
|
16
16
|
'request[slug]': slug,
|
|
17
17
|
},
|
|
18
|
+
timeout: { request: WP_ORG_TIMEOUT_MS },
|
|
18
19
|
})
|
|
19
20
|
.json();
|
|
20
21
|
}
|
|
@@ -27,44 +28,23 @@ export async function resolvePluginVersion(slug, version) {
|
|
|
27
28
|
}
|
|
28
29
|
export default class Add extends LoopressCommand {
|
|
29
30
|
static args = {
|
|
30
|
-
slug: Args.string({ description: 'Plugin slug
|
|
31
|
+
slug: Args.string({ description: 'Plugin slug on WordPress.org', required: true }),
|
|
31
32
|
version: Args.string({ description: 'Version to pin (default: latest)' }),
|
|
32
33
|
};
|
|
33
|
-
static description = 'Add a plugin to loopress.json
|
|
34
|
+
static description = 'Add a WordPress.org plugin to loopress.json';
|
|
34
35
|
static examples = [
|
|
35
36
|
'$ lps plugin add woocommerce',
|
|
36
37
|
'$ lps plugin add woocommerce 8.9.1',
|
|
37
|
-
'$ lps plugin add wpackagist-plugin/advanced-custom-fields',
|
|
38
38
|
'$ lps plugin add contact-form-7 --dry-run',
|
|
39
39
|
];
|
|
40
40
|
static flags = {
|
|
41
41
|
...LoopressCommand.baseFlags,
|
|
42
|
-
|
|
42
|
+
...LoopressCommand.dryRunFlag,
|
|
43
43
|
};
|
|
44
44
|
async run() {
|
|
45
|
-
const { args
|
|
46
|
-
const dryRun = flags['dry-run'];
|
|
45
|
+
const { args } = await this.parse(Add);
|
|
47
46
|
const { slug } = args;
|
|
48
47
|
const requestedVersion = args.version ?? 'latest';
|
|
49
|
-
if (slug.includes('/')) {
|
|
50
|
-
await this.requireComposerPackage(slug, requestedVersion, dryRun);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
await this.requireWpOrgPlugin(slug, requestedVersion, dryRun);
|
|
54
|
-
}
|
|
55
|
-
async requireComposerPackage(pkg, version, dryRun) {
|
|
56
|
-
const composerArg = version === 'latest' ? pkg : `${pkg}:${version}`;
|
|
57
|
-
this.log(`Running: composer require ${composerArg}`);
|
|
58
|
-
if (dryRun) {
|
|
59
|
-
this.log(`[dry-run] Would run: composer require ${composerArg}`);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const result = spawnSync('composer', ['require', composerArg], { stdio: 'inherit' });
|
|
63
|
-
if (result.status !== 0) {
|
|
64
|
-
this.error('composer require failed. Make sure Composer is installed and accessible.');
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
async requireWpOrgPlugin(slug, requestedVersion, dryRun) {
|
|
68
48
|
this.log(`Resolving ${slug}@${requestedVersion}...`);
|
|
69
49
|
let resolvedVersion;
|
|
70
50
|
try {
|
|
@@ -74,20 +54,19 @@ export default class Add extends LoopressCommand {
|
|
|
74
54
|
this.error(error.message);
|
|
75
55
|
}
|
|
76
56
|
this.log(`Resolved: ${slug}@${resolvedVersion}`);
|
|
77
|
-
const
|
|
78
|
-
const existing = localConfig.plugins ?? {};
|
|
57
|
+
const existing = this.localConfig.plugins ?? {};
|
|
79
58
|
if (existing[slug] === resolvedVersion) {
|
|
80
59
|
this.log(`${slug}@${resolvedVersion} is already in loopress.json, nothing to do.`);
|
|
81
60
|
return;
|
|
82
61
|
}
|
|
83
62
|
const updated = existing[slug] !== undefined;
|
|
84
63
|
const label = updated ? `${existing[slug]} → ${resolvedVersion}` : resolvedVersion;
|
|
85
|
-
if (dryRun) {
|
|
64
|
+
if (this.dryRun) {
|
|
86
65
|
this.log(`[dry-run] Would ${updated ? 'update' : 'add'} ${slug}: ${label}`);
|
|
87
66
|
return;
|
|
88
67
|
}
|
|
89
68
|
await writeLocalConfig({
|
|
90
|
-
...localConfig,
|
|
69
|
+
...this.localConfig,
|
|
91
70
|
plugins: { ...existing, [slug]: resolvedVersion },
|
|
92
71
|
});
|
|
93
72
|
this.log(`${updated ? 'Updated' : 'Added'} ${slug}: ${label}`);
|
|
@@ -1,23 +1,18 @@
|
|
|
1
|
-
import { Flags } from '@oclif/core';
|
|
2
|
-
import got from 'got';
|
|
3
1
|
import { LoopressCommand } from '../../lib/base.js';
|
|
4
2
|
import { getComposerManagedSlugs, readComposerJson } from '../../utils/composer.js';
|
|
5
|
-
import {
|
|
3
|
+
import { writeLocalConfig } from '../../utils/loopress-config.js';
|
|
6
4
|
import { mergePluginManifest } from '../../utils/plugins.js';
|
|
7
5
|
export default class Pull extends LoopressCommand {
|
|
8
6
|
static description = 'Pull installed plugins from WordPress into loopress.json';
|
|
9
|
-
static examples = ['$ lps
|
|
7
|
+
static examples = ['$ lps plugin pull', '$ lps plugin pull --dry-run'];
|
|
10
8
|
static flags = {
|
|
11
9
|
...LoopressCommand.baseFlags,
|
|
12
|
-
|
|
10
|
+
...LoopressCommand.dryRunFlag,
|
|
13
11
|
};
|
|
14
12
|
async run() {
|
|
15
|
-
const { flags } = await this.parse(Pull);
|
|
16
|
-
const dryRun = flags['dry-run'];
|
|
17
13
|
const { url } = this.siteConfig;
|
|
18
14
|
this.log(`Pulling plugins from ${url}`);
|
|
19
|
-
const
|
|
20
|
-
const installed = await got.get(`${url}/wp-json/loopress/v1/plugins`, { headers }).json();
|
|
15
|
+
const installed = await this.wp.get('loopress/v1/plugins');
|
|
21
16
|
const composerJson = await readComposerJson();
|
|
22
17
|
const composerSlugs = composerJson ? getComposerManagedSlugs(composerJson) : [];
|
|
23
18
|
const incoming = Object.fromEntries(installed.filter((p) => !composerSlugs.includes(p.slug)).map((p) => [p.slug, p.version]));
|
|
@@ -27,9 +22,8 @@ export default class Pull extends LoopressCommand {
|
|
|
27
22
|
this.log(`Skipping ${found.length} Composer-managed ${found.length === 1 ? 'plugin' : 'plugins'}: ${found.join(', ')}`);
|
|
28
23
|
}
|
|
29
24
|
}
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
if (dryRun) {
|
|
25
|
+
const { added, merged, updated } = mergePluginManifest(this.localConfig.plugins ?? {}, incoming);
|
|
26
|
+
if (this.dryRun) {
|
|
33
27
|
this.log(`[dry-run] Would write ${Object.keys(merged).length} plugins to loopress.json`);
|
|
34
28
|
if (added.length > 0)
|
|
35
29
|
this.log(` + ${added.join(', ')}`);
|
|
@@ -39,19 +33,14 @@ export default class Pull extends LoopressCommand {
|
|
|
39
33
|
}
|
|
40
34
|
return;
|
|
41
35
|
}
|
|
42
|
-
await writeLocalConfig({ ...localConfig, plugins: merged });
|
|
36
|
+
await writeLocalConfig({ ...this.localConfig, plugins: merged });
|
|
43
37
|
this.log(`Wrote ${Object.keys(merged).length} plugins to loopress.json`);
|
|
44
38
|
if (added.length > 0)
|
|
45
39
|
this.log(` + Added: ${added.join(', ')}`);
|
|
46
40
|
for (const u of updated)
|
|
47
41
|
this.log(` ~ Updated: ${u.slug} ${u.from} → ${u.to}`);
|
|
48
42
|
if (Object.keys(merged).length > 0) {
|
|
49
|
-
await
|
|
50
|
-
.post(`${url}/wp-json/loopress/v1/plugins/auto-updates/disable`, {
|
|
51
|
-
headers,
|
|
52
|
-
json: { slugs: Object.keys(merged) },
|
|
53
|
-
})
|
|
54
|
-
.json();
|
|
43
|
+
await this.wp.post('loopress/v1/plugins/auto-updates/disable', { slugs: Object.keys(merged) });
|
|
55
44
|
}
|
|
56
45
|
}
|
|
57
46
|
}
|
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
import { confirm } from '@inquirer/prompts';
|
|
2
|
-
import { Flags } from '@oclif/core';
|
|
3
|
-
import got from 'got';
|
|
4
2
|
import { PushCommand } from '../../lib/push-command.js';
|
|
5
3
|
import { getComposerManagedSlugs, readComposerJson } from '../../utils/composer.js';
|
|
6
|
-
import { readLocalConfig } from '../../utils/loopress-config.js';
|
|
7
4
|
import { diffPlugins } from '../../utils/plugins.js';
|
|
8
5
|
export default class Push extends PushCommand {
|
|
9
|
-
static description = '
|
|
10
|
-
static examples = ['$ lps
|
|
6
|
+
static description = 'Push plugins to WordPress to match loopress.json';
|
|
7
|
+
static examples = ['$ lps plugin push', '$ lps plugin push --dry-run'];
|
|
11
8
|
static flags = {
|
|
12
9
|
...PushCommand.baseFlags,
|
|
13
|
-
|
|
10
|
+
...PushCommand.dryRunFlag,
|
|
14
11
|
};
|
|
15
12
|
async run() {
|
|
16
|
-
const { flags } = await this.parse(Push);
|
|
17
|
-
const dryRun = flags['dry-run'];
|
|
18
|
-
this.dryRun = dryRun;
|
|
19
13
|
const { url } = this.siteConfig;
|
|
20
|
-
const
|
|
21
|
-
const manifest = localConfig.plugins;
|
|
14
|
+
const manifest = this.localConfig.plugins;
|
|
22
15
|
if (!manifest || Object.keys(manifest).length === 0) {
|
|
23
|
-
this.error('No plugins found in loopress.json. Run `lps
|
|
16
|
+
this.error('No plugins found in loopress.json. Run `lps plugin pull` first.');
|
|
24
17
|
}
|
|
25
18
|
const composerJson = await readComposerJson();
|
|
26
19
|
const composerSlugs = composerJson ? getComposerManagedSlugs(composerJson) : [];
|
|
@@ -31,8 +24,7 @@ export default class Push extends PushCommand {
|
|
|
31
24
|
this.log('Run `lps composer push` to deploy them.');
|
|
32
25
|
}
|
|
33
26
|
this.log(`Pushing plugins to ${url}`);
|
|
34
|
-
const
|
|
35
|
-
const installed = await got.get(`${url}/wp-json/loopress/v1/plugins`, { headers }).json();
|
|
27
|
+
const installed = await this.wp.get('loopress/v1/plugins');
|
|
36
28
|
const { drifted, toActivate, toInstall } = diffPlugins(filteredManifest, installed);
|
|
37
29
|
if (toInstall.length === 0 && toActivate.length === 0 && drifted.length === 0) {
|
|
38
30
|
this.log('Everything is already in sync.');
|
|
@@ -54,30 +46,17 @@ export default class Push extends PushCommand {
|
|
|
54
46
|
this.log(` ~ ${a.slug}: site has ${a.currentVersion}, manifest wants ${a.targetVersion}`);
|
|
55
47
|
}
|
|
56
48
|
}
|
|
57
|
-
if (dryRun)
|
|
49
|
+
if (this.dryRun)
|
|
58
50
|
return;
|
|
59
51
|
// Install missing plugins and activate them.
|
|
60
52
|
for (const action of toInstall) {
|
|
61
53
|
this.log(`\nInstalling ${action.slug} @ ${action.targetVersion}...`);
|
|
62
|
-
|
|
63
|
-
const result = await got
|
|
64
|
-
.post(`${url}/wp-json/loopress/v1/plugins/install`, {
|
|
65
|
-
headers,
|
|
66
|
-
json: { slug: action.slug, version: action.targetVersion },
|
|
67
|
-
})
|
|
68
|
-
.json();
|
|
69
|
-
this.log(` ✓ ${result.message}`);
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
this.warn(` Failed to install ${action.slug}: ${error.message}`);
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
await this.activatePlugin(url, headers, action.slug);
|
|
54
|
+
await this.installAndActivate(action.slug, action.targetVersion);
|
|
76
55
|
}
|
|
77
56
|
// Activate installed-but-inactive plugins without prompting.
|
|
78
57
|
for (const action of toActivate) {
|
|
79
58
|
this.log(`\nActivating ${action.slug}...`);
|
|
80
|
-
await this.activatePlugin(
|
|
59
|
+
await this.activatePlugin(action.slug);
|
|
81
60
|
}
|
|
82
61
|
// Prompt per drifted plugin before syncing.
|
|
83
62
|
for (const action of drifted) {
|
|
@@ -91,32 +70,28 @@ export default class Push extends PushCommand {
|
|
|
91
70
|
continue;
|
|
92
71
|
}
|
|
93
72
|
this.log(` Syncing ${action.slug} to ${action.targetVersion}...`);
|
|
94
|
-
|
|
95
|
-
const result = await got
|
|
96
|
-
.post(`${url}/wp-json/loopress/v1/plugins/install`, {
|
|
97
|
-
headers,
|
|
98
|
-
json: { slug: action.slug, version: action.targetVersion },
|
|
99
|
-
})
|
|
100
|
-
.json();
|
|
101
|
-
this.log(` ✓ ${result.message}`);
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
this.warn(` Failed to sync ${action.slug}: ${error.message}`);
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
await this.activatePlugin(url, headers, action.slug);
|
|
73
|
+
await this.installAndActivate(action.slug, action.targetVersion);
|
|
108
74
|
}
|
|
109
75
|
await this.recordSuccess();
|
|
110
76
|
}
|
|
111
|
-
async activatePlugin(
|
|
77
|
+
async activatePlugin(slug) {
|
|
112
78
|
try {
|
|
113
|
-
const result = await
|
|
114
|
-
.post(`${url}/wp-json/loopress/v1/plugins/activate`, { headers, json: { slug } })
|
|
115
|
-
.json();
|
|
79
|
+
const result = await this.wp.post('loopress/v1/plugins/activate', { slug });
|
|
116
80
|
this.log(` ✓ ${result.message}`);
|
|
117
81
|
}
|
|
118
82
|
catch (error) {
|
|
119
83
|
this.warn(` Failed to activate ${slug}: ${error.message}`);
|
|
120
84
|
}
|
|
121
85
|
}
|
|
86
|
+
async installAndActivate(slug, version) {
|
|
87
|
+
try {
|
|
88
|
+
const result = await this.wp.post('loopress/v1/plugins/install', { slug, version });
|
|
89
|
+
this.log(` ✓ ${result.message}`);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
this.warn(` Failed to install ${slug}: ${error.message}`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
await this.activatePlugin(slug);
|
|
96
|
+
}
|
|
122
97
|
}
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import { confirm, input, password as passwordPrompt, select } from '@inquirer/prompts';
|
|
2
2
|
import { Command } from '@oclif/core';
|
|
3
3
|
import { configManager } from '../../config/project-config.manager.js';
|
|
4
|
+
const NEW_PROJECT = '__new__';
|
|
4
5
|
export default class Config extends Command {
|
|
5
6
|
static description = 'Add or update a WordPress project environment';
|
|
6
7
|
static examples = ['$ lps project config'];
|
|
7
8
|
async run() {
|
|
8
9
|
await this.parse(Config);
|
|
9
|
-
const projectName = await
|
|
10
|
-
message: 'Project name (identifier, no spaces)',
|
|
11
|
-
validate(value) {
|
|
12
|
-
if (!/^[a-z0-9_-]+$/.test(value)) {
|
|
13
|
-
return 'Name must be lowercase with only letters, numbers, - and _';
|
|
14
|
-
}
|
|
15
|
-
return true;
|
|
16
|
-
},
|
|
17
|
-
});
|
|
10
|
+
const { projectId, projectName } = await this.resolveProject();
|
|
18
11
|
const envChoice = await select({
|
|
19
12
|
choices: [
|
|
20
13
|
{ name: 'local', value: 'local' },
|
|
@@ -30,8 +23,7 @@ export default class Config extends Command {
|
|
|
30
23
|
validate: (value) => (value.trim().length > 0 ? true : 'Name cannot be empty'),
|
|
31
24
|
})
|
|
32
25
|
: envChoice;
|
|
33
|
-
const
|
|
34
|
-
const existingEnv = existingProject?.environments[envName];
|
|
26
|
+
const existingEnv = configManager.getEnvironment(projectId, envName);
|
|
35
27
|
if (existingEnv) {
|
|
36
28
|
const overwrite = await confirm({
|
|
37
29
|
default: false,
|
|
@@ -73,20 +65,47 @@ export default class Config extends Command {
|
|
|
73
65
|
token,
|
|
74
66
|
url,
|
|
75
67
|
};
|
|
76
|
-
if (
|
|
77
|
-
configManager.setEnvironment(
|
|
68
|
+
if (configManager.getProject(projectId)) {
|
|
69
|
+
configManager.setEnvironment(projectId, envName, env);
|
|
78
70
|
}
|
|
79
71
|
else {
|
|
80
72
|
const project = {
|
|
81
73
|
addedAt: new Date().toISOString(),
|
|
82
|
-
currentEnv: envName,
|
|
83
74
|
environments: { [envName]: env },
|
|
84
75
|
name: projectName,
|
|
85
76
|
};
|
|
86
|
-
configManager.setProject(
|
|
77
|
+
configManager.setProject(projectId, project);
|
|
87
78
|
}
|
|
88
79
|
this.log(`✓ "${projectName}/${envName}" configured`);
|
|
89
|
-
this.log('→ Run `lps project switch` to change active project');
|
|
90
|
-
|
|
80
|
+
this.log('→ Run `lps project switch` to change the active project or environment');
|
|
81
|
+
}
|
|
82
|
+
async resolveProject() {
|
|
83
|
+
const projects = configManager.listProjects();
|
|
84
|
+
if (projects.length > 0) {
|
|
85
|
+
const choice = await select({
|
|
86
|
+
choices: [
|
|
87
|
+
...projects.map((project) => ({ name: project.name, value: project.id })),
|
|
88
|
+
{ name: 'Add a new project…', value: NEW_PROJECT },
|
|
89
|
+
],
|
|
90
|
+
message: 'Project',
|
|
91
|
+
});
|
|
92
|
+
if (choice !== NEW_PROJECT) {
|
|
93
|
+
const project = projects.find((p) => p.id === choice);
|
|
94
|
+
return { projectId: project.id, projectName: project.name };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const existingNames = new Set(projects.map((project) => project.name.trim().toLowerCase()));
|
|
98
|
+
const projectName = await input({
|
|
99
|
+
message: 'Project name',
|
|
100
|
+
validate(value) {
|
|
101
|
+
const trimmed = value.trim();
|
|
102
|
+
if (trimmed.length === 0)
|
|
103
|
+
return 'Name cannot be empty';
|
|
104
|
+
if (existingNames.has(trimmed.toLowerCase()))
|
|
105
|
+
return `A project named "${trimmed}" already exists`;
|
|
106
|
+
return true;
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
return { projectId: configManager.createProjectId(), projectName: projectName.trim() };
|
|
91
110
|
}
|
|
92
111
|
}
|
|
@@ -12,17 +12,16 @@ export default class List extends Command {
|
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
for (const project of projects) {
|
|
15
|
-
const envs =
|
|
15
|
+
const envs = configManager.listEnvironments(project.id);
|
|
16
16
|
const marker = project.isCurrent ? c('green', '●') : c('dim', '○');
|
|
17
17
|
const name = project.isCurrent ? c('green', project.name) : project.name;
|
|
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
|
|
22
|
-
const
|
|
23
|
-
const envName = isActiveEnv ? c('cyan', env.name.padEnd(15)) : c('dim', env.name.padEnd(15));
|
|
21
|
+
const envMarker = env.isCurrent ? c('cyan', '·') : c('dim', '·');
|
|
22
|
+
const envName = env.isCurrent ? c('cyan', env.name.padEnd(15)) : c('dim', env.name.padEnd(15));
|
|
24
23
|
const envUrl = c('dim', env.url);
|
|
25
|
-
const activeTag =
|
|
24
|
+
const activeTag = env.isCurrent ? ` ${c('cyan', '←')}` : '';
|
|
26
25
|
this.log(` ${envMarker} ${envName} ${envUrl}${activeTag}`);
|
|
27
26
|
}
|
|
28
27
|
this.log('');
|
|
@@ -2,7 +2,7 @@ import { checkbox } from '@inquirer/prompts';
|
|
|
2
2
|
import { Command } from '@oclif/core';
|
|
3
3
|
import { configManager } from '../../config/project-config.manager.js';
|
|
4
4
|
export default class Remove extends Command {
|
|
5
|
-
static description = 'Remove one or more WordPress
|
|
5
|
+
static description = 'Remove one or more WordPress projects or environments';
|
|
6
6
|
static examples = ['$ lps project remove'];
|
|
7
7
|
async run() {
|
|
8
8
|
await this.parse(Remove);
|
|
@@ -10,25 +10,44 @@ export default class Remove extends Command {
|
|
|
10
10
|
if (projects.length === 0) {
|
|
11
11
|
this.error('No projects configured.');
|
|
12
12
|
}
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const targets = [];
|
|
14
|
+
const choices = projects.flatMap((project) => {
|
|
15
|
+
const envCount = Object.keys(project.environments).length;
|
|
16
|
+
const envLabel = `${envCount} env${envCount > 1 ? 's' : ''}`;
|
|
17
|
+
const currentMarker = project.isCurrent ? ' [current]' : '';
|
|
18
|
+
targets.push({ kind: 'project', projectId: project.id, projectName: project.name });
|
|
19
|
+
const projectChoice = {
|
|
20
|
+
name: `${project.isCurrent ? '●' : '○'} ${project.name.padEnd(20)} (${envLabel})${currentMarker}`,
|
|
21
|
+
value: String(targets.length - 1),
|
|
22
|
+
};
|
|
23
|
+
const envChoices = configManager.listEnvironments(project.id).map((env) => {
|
|
24
|
+
targets.push({ env: env.name, kind: 'env', projectId: project.id, projectName: project.name });
|
|
18
25
|
return {
|
|
19
|
-
name:
|
|
20
|
-
value:
|
|
26
|
+
name: ` ${env.isCurrent ? '●' : '○'} ${env.name.padEnd(20)} ${env.url}${env.isCurrent ? ' [current]' : ''}`,
|
|
27
|
+
value: String(targets.length - 1),
|
|
21
28
|
};
|
|
22
|
-
})
|
|
23
|
-
|
|
29
|
+
});
|
|
30
|
+
return [projectChoice, ...envChoices];
|
|
31
|
+
});
|
|
32
|
+
const chosen = await checkbox({
|
|
33
|
+
choices,
|
|
34
|
+
message: 'Select projects or environments to remove',
|
|
24
35
|
});
|
|
25
36
|
if (chosen.length === 0) {
|
|
26
37
|
this.log('Nothing removed.');
|
|
27
38
|
return;
|
|
28
39
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
const selected = chosen.map((index) => targets[Number(index)]);
|
|
41
|
+
const projectsToRemove = new Map(selected.filter((target) => target.kind === 'project').map((target) => [target.projectId, target.projectName]));
|
|
42
|
+
const envsToRemove = selected.filter((target) => target.kind === 'env' && !projectsToRemove.has(target.projectId));
|
|
43
|
+
for (const projectId of projectsToRemove.keys())
|
|
44
|
+
configManager.removeProject(projectId);
|
|
45
|
+
for (const { env, projectId } of envsToRemove)
|
|
46
|
+
configManager.removeEnvironment(projectId, env);
|
|
47
|
+
const removedLabels = [
|
|
48
|
+
...projectsToRemove.values(),
|
|
49
|
+
...envsToRemove.map(({ env, projectName }) => `${projectName}/${env}`),
|
|
50
|
+
];
|
|
51
|
+
this.log(`✓ Removed: ${removedLabels.join(', ')}`);
|
|
33
52
|
}
|
|
34
53
|
}
|
|
@@ -2,7 +2,7 @@ import { select } from '@inquirer/prompts';
|
|
|
2
2
|
import { Command } from '@oclif/core';
|
|
3
3
|
import { configManager } from '../../config/project-config.manager.js';
|
|
4
4
|
export default class Switch extends Command {
|
|
5
|
-
static description = 'Switch the active project';
|
|
5
|
+
static description = 'Switch the active project and environment';
|
|
6
6
|
static examples = ['$ lps project switch'];
|
|
7
7
|
async run() {
|
|
8
8
|
await this.parse(Switch);
|
|
@@ -10,11 +10,30 @@ export default class Switch extends Command {
|
|
|
10
10
|
if (projects.length === 0) {
|
|
11
11
|
this.error('No projects configured. Run `lps project config` first.');
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const { id: projectId, name: projectName } = await this.resolveProject(projects);
|
|
14
|
+
const envName = await this.resolveEnvironment(projectId, projectName);
|
|
15
|
+
configManager.setCurrent(projectId, envName);
|
|
16
|
+
this.log(`✓ Switched to "${projectName}/${envName}"`);
|
|
17
|
+
}
|
|
18
|
+
async resolveEnvironment(projectId, projectName) {
|
|
19
|
+
const envs = configManager.listEnvironments(projectId);
|
|
20
|
+
if (envs.length === 0) {
|
|
21
|
+
this.error(`No environments configured for "${projectName}". Run \`lps project config\` first.`);
|
|
17
22
|
}
|
|
23
|
+
if (envs.length === 1)
|
|
24
|
+
return envs[0].name;
|
|
25
|
+
return select({
|
|
26
|
+
choices: envs.map((env) => ({
|
|
27
|
+
name: `${env.isCurrent ? '●' : '○'} ${env.name.padEnd(20)} ${env.url}${env.isCurrent ? ' [current]' : ''}`,
|
|
28
|
+
value: env.name,
|
|
29
|
+
})),
|
|
30
|
+
default: envs.find((env) => env.isCurrent)?.name,
|
|
31
|
+
message: `Select environment for "${projectName}"`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async resolveProject(projects) {
|
|
35
|
+
if (projects.length === 1)
|
|
36
|
+
return { id: projects[0].id, name: projects[0].name };
|
|
18
37
|
const chosen = await select({
|
|
19
38
|
choices: projects.map((project) => {
|
|
20
39
|
const envCount = Object.keys(project.environments).length;
|
|
@@ -22,13 +41,13 @@ export default class Switch extends Command {
|
|
|
22
41
|
const currentMarker = project.isCurrent ? ' [current]' : '';
|
|
23
42
|
return {
|
|
24
43
|
name: `${project.isCurrent ? '●' : '○'} ${project.name.padEnd(20)} (${envLabel})${currentMarker}`,
|
|
25
|
-
value: project.
|
|
44
|
+
value: project.id,
|
|
26
45
|
};
|
|
27
46
|
}),
|
|
28
|
-
default: projects.find((
|
|
47
|
+
default: projects.find((project) => project.isCurrent)?.id,
|
|
29
48
|
message: 'Select active project',
|
|
30
49
|
});
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
const project = projects.find((p) => p.id === chosen);
|
|
51
|
+
return { id: project.id, name: project.name };
|
|
33
52
|
}
|
|
34
53
|
}
|
|
@@ -3,8 +3,8 @@ export default class List extends LoopressCommand {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
|
-
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
6
|
plugin: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
8
|
password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
9
|
url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -1,59 +1,44 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import got from 'got';
|
|
3
2
|
import { LoopressCommand } from '../../lib/base.js';
|
|
3
|
+
import { snippetPluginFlag } from '../../utils/snippet-plugin-flag.js';
|
|
4
4
|
import { getSnippetPlugin } from '../../utils/snippet-plugin.js';
|
|
5
5
|
export default class List extends LoopressCommand {
|
|
6
6
|
static description = 'List snippets from WordPress';
|
|
7
7
|
static examples = [
|
|
8
|
-
'$ lps
|
|
9
|
-
'$ lps
|
|
10
|
-
'$ lps
|
|
8
|
+
'$ lps snippet list',
|
|
9
|
+
'$ lps snippet list --url http://example.com',
|
|
10
|
+
'$ lps snippet list --plugin wpcode',
|
|
11
11
|
];
|
|
12
12
|
static flags = {
|
|
13
13
|
...LoopressCommand.baseFlags,
|
|
14
14
|
json: Flags.boolean({ char: 'j', description: 'Output in JSON format' }),
|
|
15
|
-
|
|
16
|
-
char: 'p',
|
|
17
|
-
description: 'WordPress snippet plugin to target (overrides loopress.json)',
|
|
18
|
-
options: ['code-snippets', 'wpcode'],
|
|
19
|
-
}),
|
|
15
|
+
...snippetPluginFlag,
|
|
20
16
|
};
|
|
21
17
|
async run() {
|
|
22
18
|
const { flags } = await this.parse(List);
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
19
|
+
const adapter = getSnippetPlugin(this.resolveSnippetPlugin(flags.plugin));
|
|
20
|
+
const remoteList = await this.wp.get(adapter.endpointPath());
|
|
21
|
+
const snippets = remoteList.map((r) => adapter.fromRemote(r));
|
|
22
|
+
if (flags.json) {
|
|
23
|
+
this.log(JSON.stringify(snippets, null, 2));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (snippets.length === 0) {
|
|
27
|
+
this.log('No snippets found');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.log(`Found ${snippets.length} snippet${snippets.length === 1 ? '' : 's'}:`);
|
|
31
|
+
this.log('');
|
|
32
|
+
for (const snippet of snippets) {
|
|
33
|
+
this.log(` ${snippet.id}. ${snippet.name}`);
|
|
34
|
+
this.log(` Active: ${snippet.active ? 'yes' : 'no'}`);
|
|
35
|
+
if (snippet.tags.length > 0) {
|
|
36
|
+
this.log(` Tags: ${snippet.tags.join(', ')}`);
|
|
34
37
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.log('No snippets found');
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
this.log(`Found ${snippets.length} snippet${snippets.length === 1 ? '' : 's'}:`);
|
|
41
|
-
console.log('');
|
|
42
|
-
for (const snippet of snippets) {
|
|
43
|
-
this.log(` ${snippet.id}. ${snippet.name}`);
|
|
44
|
-
this.log(` Active: ${snippet.active ? '✓' : '✗'}`);
|
|
45
|
-
if (snippet.tags && snippet.tags.length > 0) {
|
|
46
|
-
this.log(` Tags: ${snippet.tags.join(', ')}`);
|
|
47
|
-
}
|
|
48
|
-
if (snippet.description) {
|
|
49
|
-
this.log(` Description: ${snippet.description}`);
|
|
50
|
-
}
|
|
51
|
-
console.log('');
|
|
52
|
-
}
|
|
38
|
+
if (snippet.description) {
|
|
39
|
+
this.log(` Description: ${snippet.description}`);
|
|
53
40
|
}
|
|
54
|
-
|
|
55
|
-
catch (error) {
|
|
56
|
-
this.error(`❌ Error listing snippets: ${error.message}`);
|
|
41
|
+
this.log('');
|
|
57
42
|
}
|
|
58
43
|
}
|
|
59
44
|
}
|
|
@@ -9,8 +9,8 @@ export default class Pull extends LoopressCommand {
|
|
|
9
9
|
static description: string;
|
|
10
10
|
static examples: string[];
|
|
11
11
|
static flags: {
|
|
12
|
-
dryRun: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
12
|
plugin: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
14
|
password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
15
|
url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
16
|
user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|