@pagepocket/cli 0.10.1 → 0.11.1
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/dist/commands/archive.js +146 -76
- package/dist/commands/plugin/add.js +19 -25
- package/dist/commands/plugin/doctor.js +22 -28
- package/dist/commands/plugin/ls.js +16 -22
- package/dist/commands/plugin/prune.js +13 -19
- package/dist/commands/plugin/remove.js +16 -22
- package/dist/commands/plugin/set.js +13 -19
- package/dist/commands/plugin/uninstall.js +29 -35
- package/dist/commands/plugin/update.js +22 -28
- package/dist/commands/strategy/add.js +14 -20
- package/dist/commands/strategy/doctor.js +11 -17
- package/dist/commands/strategy/ls.js +22 -0
- package/dist/commands/strategy/pin.js +10 -16
- package/dist/commands/strategy/remove.js +10 -16
- package/dist/commands/strategy/update.js +21 -27
- package/dist/commands/view.js +36 -42
- package/dist/index.js +9 -7
- package/dist/lib/filename.js +1 -5
- package/dist/services/config-service.js +24 -30
- package/dist/services/load-configured-plugins.js +8 -12
- package/dist/services/plugin-installer.js +6 -12
- package/dist/services/plugin-store.js +27 -68
- package/dist/services/strategy/builtin-strategy-registry.js +23 -0
- package/dist/services/strategy/strategy-analyze.js +10 -20
- package/dist/services/strategy/strategy-config.js +6 -13
- package/dist/services/strategy/strategy-fetch.js +11 -18
- package/dist/services/strategy/strategy-io.js +15 -25
- package/dist/services/strategy/strategy-normalize.js +4 -8
- package/dist/services/strategy/strategy-pack-read.js +10 -17
- package/dist/services/strategy/strategy-pack-store.js +12 -18
- package/dist/services/strategy/strategy-service.js +79 -82
- package/dist/services/strategy/types.js +1 -2
- package/dist/services/units/unit-store.js +16 -20
- package/dist/services/units/unit-validate.js +20 -5
- package/dist/services/user-packages/parse-pinned-spec.js +6 -6
- package/dist/services/user-packages/user-package-installer.js +4 -9
- package/dist/services/user-packages/user-package-store.js +19 -57
- package/dist/stages/prepare-output.js +6 -13
- package/dist/units/network-observer-unit.js +7 -10
- package/dist/utils/array.js +3 -0
- package/dist/utils/normalize-argv.js +3 -8
- package/dist/utils/parse-json.js +1 -5
- package/dist/utils/parse-plugin-options.js +1 -5
- package/dist/utils/parse-plugin-spec.js +6 -6
- package/dist/utils/validate-plugin-default-export.js +1 -5
- package/dist/utils/with-spinner.js +3 -10
- package/dist/view.js +12 -19
- package/package.json +12 -11
- package/dist/services/strategy/read-installed-package-version.js +0 -28
package/dist/commands/archive.js
CHANGED
|
@@ -1,28 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { Args, Command, Flags } from "@oclif/core";
|
|
4
|
+
import { ga, ns, PagePocket } from "@pagepocket/lib";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { loadConfiguredPlugins } from "../services/load-configured-plugins.js";
|
|
7
|
+
import { ConfigService } from "../services/config-service.js";
|
|
8
|
+
import { readBuiltinStrategy, listBuiltinStrategyNames } from "../services/strategy/builtin-strategy-registry.js";
|
|
9
|
+
import { StrategyService } from "../services/strategy/strategy-service.js";
|
|
10
|
+
import { normalizeStrategyUnits } from "../services/strategy/strategy-normalize.js";
|
|
11
|
+
import { parsePinnedSpec } from "../services/user-packages/parse-pinned-spec.js";
|
|
12
|
+
import { isUnitLike, resolveUnitConstructor } from "../services/units/unit-validate.js";
|
|
13
|
+
import { UnitStore } from "../services/units/unit-store.js";
|
|
14
|
+
import { uniq } from "../utils/array.js";
|
|
15
|
+
import { withSpinner } from "../utils/with-spinner.js";
|
|
16
|
+
const buildMissingStrategyError = (input) => {
|
|
17
|
+
const available = uniq([...input.installedNames, ...listBuiltinStrategyNames()])
|
|
18
|
+
.filter((name) => name.trim().length > 0)
|
|
19
|
+
.sort((left, right) => left.localeCompare(right));
|
|
20
|
+
const suffix = available.length > 0
|
|
21
|
+
? ` Available strategies: ${available.join(", ")}`
|
|
22
|
+
: " No strategies found.";
|
|
23
|
+
return new Error(`Strategy not found: ${input.strategyName}.${suffix}`);
|
|
4
24
|
};
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
const resolveStrategy = (input) => {
|
|
26
|
+
const installedNames = input.strategyService.listInstalledStrategyNames();
|
|
27
|
+
if (installedNames.includes(input.strategyName)) {
|
|
28
|
+
return {
|
|
29
|
+
name: input.strategyName,
|
|
30
|
+
strategyFile: input.strategyService.readStrategy(input.strategyName),
|
|
31
|
+
source: "installed"
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const builtin = readBuiltinStrategy(input.strategyName);
|
|
35
|
+
if (builtin) {
|
|
36
|
+
return {
|
|
37
|
+
name: input.strategyName,
|
|
38
|
+
strategyFile: builtin,
|
|
39
|
+
source: "builtin"
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
throw buildMissingStrategyError({
|
|
43
|
+
strategyName: input.strategyName,
|
|
44
|
+
installedNames
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const assertNoDuplicateUnitIds = (input) => {
|
|
48
|
+
const seen = new Set();
|
|
49
|
+
const dup = input.units.find((u) => {
|
|
50
|
+
if (seen.has(u.id)) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
seen.add(u.id);
|
|
54
|
+
return false;
|
|
55
|
+
});
|
|
56
|
+
if (dup) {
|
|
57
|
+
throw new Error(`Duplicate unit id detected in strategy ${input.strategyName}: ${dup.id}`);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const loadInstalledStrategyUnits = async (input) => {
|
|
61
|
+
const strategyService = new StrategyService(input.configService);
|
|
62
|
+
const unitStore = new UnitStore(input.configService);
|
|
20
63
|
strategyService.ensureConfigFileExists();
|
|
21
64
|
const strategyFile = strategyService.readStrategy(input.strategyName);
|
|
22
|
-
const normalized =
|
|
65
|
+
const normalized = normalizeStrategyUnits(strategyFile);
|
|
23
66
|
const installed = unitStore.readInstalledDependencyVersions();
|
|
24
67
|
const drift = normalized.flatMap((u) => {
|
|
25
|
-
const pinned =
|
|
68
|
+
const pinned = parsePinnedSpec(u.ref);
|
|
26
69
|
const installedVersion = installed[pinned.name];
|
|
27
70
|
if (!installedVersion || installedVersion !== pinned.version) {
|
|
28
71
|
return [
|
|
@@ -45,48 +88,100 @@ const loadStrategyUnits = async (input) => {
|
|
|
45
88
|
throw new Error(`Strategy drift detected (${input.strategyName}). ${details}. ` +
|
|
46
89
|
`Fix: pp strategy update ${input.strategyName} OR pp strategy pin ${input.strategyName}`);
|
|
47
90
|
}
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
seen.add(u.id);
|
|
55
|
-
return false;
|
|
91
|
+
const units = await Promise.all(normalized.map(async (u) => unitStore.instantiateFromRef(u.ref, u.args)));
|
|
92
|
+
assertNoDuplicateUnitIds({
|
|
93
|
+
strategyName: input.strategyName,
|
|
94
|
+
units
|
|
56
95
|
});
|
|
57
|
-
|
|
58
|
-
|
|
96
|
+
return units;
|
|
97
|
+
};
|
|
98
|
+
const instantiateBuiltinUnit = async (input) => {
|
|
99
|
+
const req = createRequire(import.meta.url);
|
|
100
|
+
const pinned = parsePinnedSpec(input.ref);
|
|
101
|
+
const resolved = req.resolve(pinned.name);
|
|
102
|
+
const mod = (await import(pathToFileURL(resolved).href));
|
|
103
|
+
const ctor = resolveUnitConstructor(mod);
|
|
104
|
+
if (!ctor) {
|
|
105
|
+
throw new Error(`Unit ${pinned.name} does not export a Unit constructor.`);
|
|
106
|
+
}
|
|
107
|
+
const instance = new ctor(...input.args);
|
|
108
|
+
if (!isUnitLike(instance)) {
|
|
109
|
+
throw new Error(`Unit ${pinned.name} exported constructor did not produce a Unit.`);
|
|
110
|
+
}
|
|
111
|
+
return instance;
|
|
112
|
+
};
|
|
113
|
+
const loadBuiltinStrategyUnits = async (input) => {
|
|
114
|
+
const normalized = normalizeStrategyUnits(input.strategyFile);
|
|
115
|
+
const units = await Promise.all(normalized.map(async (u) => instantiateBuiltinUnit({
|
|
116
|
+
ref: u.ref,
|
|
117
|
+
args: u.args
|
|
118
|
+
})));
|
|
119
|
+
assertNoDuplicateUnitIds({
|
|
120
|
+
strategyName: input.strategyName,
|
|
121
|
+
units
|
|
122
|
+
});
|
|
123
|
+
return units;
|
|
124
|
+
};
|
|
125
|
+
const loadStrategyUnits = async (input) => {
|
|
126
|
+
if (input.strategy.source === "installed") {
|
|
127
|
+
return loadInstalledStrategyUnits({
|
|
128
|
+
strategyName: input.strategy.name,
|
|
129
|
+
configService: input.configService
|
|
130
|
+
});
|
|
59
131
|
}
|
|
60
|
-
return
|
|
132
|
+
return loadBuiltinStrategyUnits({
|
|
133
|
+
strategyName: input.strategy.name,
|
|
134
|
+
strategyFile: input.strategy.strategyFile
|
|
135
|
+
});
|
|
61
136
|
};
|
|
62
|
-
class ArchiveCommand extends
|
|
137
|
+
export default class ArchiveCommand extends Command {
|
|
138
|
+
static description = "Archive a web page as an offline snapshot.";
|
|
139
|
+
static args = {
|
|
140
|
+
url: Args.string({
|
|
141
|
+
description: "URL to archive",
|
|
142
|
+
required: true
|
|
143
|
+
})
|
|
144
|
+
};
|
|
145
|
+
static flags = {
|
|
146
|
+
help: Flags.help({
|
|
147
|
+
char: "h"
|
|
148
|
+
}),
|
|
149
|
+
timeout: Flags.integer({
|
|
150
|
+
char: "t",
|
|
151
|
+
description: "Network idle duration in milliseconds before capture stops",
|
|
152
|
+
required: false
|
|
153
|
+
}),
|
|
154
|
+
maxDuration: Flags.integer({
|
|
155
|
+
description: "Hard max capture duration in milliseconds",
|
|
156
|
+
required: false
|
|
157
|
+
}),
|
|
158
|
+
strategy: Flags.string({
|
|
159
|
+
description: "Run an installed or built-in strategy (unit pipeline) by name"
|
|
160
|
+
})
|
|
161
|
+
};
|
|
63
162
|
async run() {
|
|
64
163
|
const { args, flags } = await this.parse(ArchiveCommand);
|
|
65
164
|
const targetUrl = args.url;
|
|
66
165
|
const timeoutMs = typeof flags.timeout === "number" ? flags.timeout : undefined;
|
|
67
166
|
const maxDurationMs = typeof flags.maxDuration === "number" ? flags.maxDuration : undefined;
|
|
68
167
|
const strategyName = typeof flags.strategy === "string" ? flags.strategy.trim() : undefined;
|
|
69
|
-
const pagepocket =
|
|
70
|
-
const result = await
|
|
168
|
+
const pagepocket = PagePocket.fromURL(targetUrl);
|
|
169
|
+
const result = await withSpinner(async (spinner) => {
|
|
71
170
|
{
|
|
72
|
-
const configService = new
|
|
171
|
+
const configService = new ConfigService();
|
|
73
172
|
const name = strategyName && strategyName.length > 0 ? strategyName : "default";
|
|
74
|
-
const strategyService = new
|
|
173
|
+
const strategyService = new StrategyService(configService);
|
|
75
174
|
strategyService.ensureConfigFileExists();
|
|
76
|
-
const
|
|
77
|
-
if (installedNames.length === 0) {
|
|
78
|
-
const v = (0, read_installed_package_version_1.readInstalledPackageVersion)("@pagepocket/builtin-strategy");
|
|
79
|
-
await strategyService.addStrategy({
|
|
80
|
-
source: `@pagepocket/builtin-strategy@${v}`,
|
|
81
|
-
force: false
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
const units = await loadStrategyUnits({
|
|
175
|
+
const strategy = resolveStrategy({
|
|
85
176
|
strategyName: name,
|
|
177
|
+
strategyService
|
|
178
|
+
});
|
|
179
|
+
const units = await loadStrategyUnits({
|
|
180
|
+
strategy,
|
|
86
181
|
configService
|
|
87
182
|
});
|
|
88
183
|
const entryTarget = pagepocket;
|
|
89
|
-
const strategyFile =
|
|
184
|
+
const strategyFile = strategy.strategyFile;
|
|
90
185
|
const captureOptions = strategyFile.pipeline.captureOptions;
|
|
91
186
|
const effectiveTimeoutMs = typeof timeoutMs === "number" ? timeoutMs : captureOptions?.timeoutMs;
|
|
92
187
|
const effectiveMaxDurationMs = typeof maxDurationMs === "number" ? maxDurationMs : captureOptions?.maxDurationMs;
|
|
@@ -95,45 +190,20 @@ class ArchiveCommand extends core_1.Command {
|
|
|
95
190
|
...(typeof effectiveMaxDurationMs === "number"
|
|
96
191
|
? { maxDurationMs: effectiveMaxDurationMs }
|
|
97
192
|
: {}),
|
|
98
|
-
blacklist: [...
|
|
193
|
+
blacklist: [...ga, ...ns],
|
|
99
194
|
units,
|
|
100
|
-
plugins: await
|
|
195
|
+
plugins: await loadConfiguredPlugins().catch(() => [])
|
|
101
196
|
});
|
|
102
197
|
return result;
|
|
103
198
|
}
|
|
104
199
|
}, "Freezing page");
|
|
105
|
-
this.log(
|
|
200
|
+
this.log(chalk.green("All done! Snapshot created."));
|
|
106
201
|
if (result.kind === "zip") {
|
|
107
|
-
this.log(`Snapshot saved to ${
|
|
202
|
+
this.log(`Snapshot saved to ${chalk.cyan(result.zip.outputPath)}`);
|
|
108
203
|
}
|
|
109
204
|
else if (result.kind === "raw") {
|
|
110
|
-
this.log(`Snapshot saved to ${
|
|
205
|
+
this.log(`Snapshot saved to ${chalk.cyan(result.outputDir)}`);
|
|
111
206
|
}
|
|
112
207
|
process.exit();
|
|
113
208
|
}
|
|
114
209
|
}
|
|
115
|
-
ArchiveCommand.description = "Archive a web page as an offline snapshot.";
|
|
116
|
-
ArchiveCommand.args = {
|
|
117
|
-
url: core_1.Args.string({
|
|
118
|
-
description: "URL to archive",
|
|
119
|
-
required: true
|
|
120
|
-
})
|
|
121
|
-
};
|
|
122
|
-
ArchiveCommand.flags = {
|
|
123
|
-
help: core_1.Flags.help({
|
|
124
|
-
char: "h"
|
|
125
|
-
}),
|
|
126
|
-
timeout: core_1.Flags.integer({
|
|
127
|
-
char: "t",
|
|
128
|
-
description: "Network idle duration in milliseconds before capture stops",
|
|
129
|
-
required: false
|
|
130
|
-
}),
|
|
131
|
-
maxDuration: core_1.Flags.integer({
|
|
132
|
-
description: "Hard max capture duration in milliseconds",
|
|
133
|
-
required: false
|
|
134
|
-
}),
|
|
135
|
-
strategy: core_1.Flags.string({
|
|
136
|
-
description: "Run an installed strategy (unit pipeline) by name"
|
|
137
|
-
})
|
|
138
|
-
};
|
|
139
|
-
exports.default = ArchiveCommand;
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const parse_plugin_spec_1 = require("../../utils/parse-plugin-spec");
|
|
13
|
-
const validate_plugin_default_export_1 = require("../../utils/validate-plugin-default-export");
|
|
14
|
-
class PluginAddCommand extends core_1.Command {
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { ConfigService } from "../../services/config-service.js";
|
|
4
|
+
import { installPluginPackage } from "../../services/plugin-installer.js";
|
|
5
|
+
import { PluginStore } from "../../services/plugin-store.js";
|
|
6
|
+
import { parsePluginOptions } from "../../utils/parse-plugin-options.js";
|
|
7
|
+
import { parsePluginSpec } from "../../utils/parse-plugin-spec.js";
|
|
8
|
+
import { validatePluginDefaultExport } from "../../utils/validate-plugin-default-export.js";
|
|
9
|
+
export default class PluginAddCommand extends Command {
|
|
10
|
+
static description = "Install a plugin package, add it to config, and run its optional setup().";
|
|
11
|
+
static strict = false;
|
|
15
12
|
async run() {
|
|
16
13
|
const [name, ...rest] = this.argv;
|
|
17
14
|
if (!name) {
|
|
@@ -21,24 +18,24 @@ class PluginAddCommand extends core_1.Command {
|
|
|
21
18
|
this.log("Usage: pp plugin add <plugin-name> [--option-* <value> ...] [--option <value> ...]");
|
|
22
19
|
return;
|
|
23
20
|
}
|
|
24
|
-
const parsedSpec =
|
|
25
|
-
const configService = new
|
|
26
|
-
const store = new
|
|
21
|
+
const parsedSpec = parsePluginSpec(name);
|
|
22
|
+
const configService = new ConfigService();
|
|
23
|
+
const store = new PluginStore(configService);
|
|
27
24
|
configService.ensureConfigFileExists();
|
|
28
25
|
const config = configService.readConfigOrDefault();
|
|
29
26
|
if (store.hasPlugin(config, parsedSpec.name)) {
|
|
30
|
-
this.log(
|
|
27
|
+
this.log(chalk.gray(`Plugin already exists in config: ${parsedSpec.name}`));
|
|
31
28
|
return;
|
|
32
29
|
}
|
|
33
|
-
const parsed =
|
|
30
|
+
const parsed = parsePluginOptions(rest);
|
|
34
31
|
const entry = parsed.kind === "none"
|
|
35
32
|
? parsedSpec.name
|
|
36
33
|
: parsed.kind === "array"
|
|
37
34
|
? { name: parsedSpec.name, spec: parsedSpec.spec, options: parsed.value }
|
|
38
35
|
: { name: parsedSpec.name, spec: parsedSpec.spec, options: parsed.value };
|
|
39
|
-
|
|
36
|
+
installPluginPackage(store, { packageName: parsedSpec.spec });
|
|
40
37
|
const mod = await store.importPluginModule(parsedSpec.name);
|
|
41
|
-
const validation =
|
|
38
|
+
const validation = validatePluginDefaultExport({
|
|
42
39
|
pluginName: parsedSpec.name,
|
|
43
40
|
moduleExports: mod,
|
|
44
41
|
options: parsed.kind === "none" ? undefined : parsed.value
|
|
@@ -49,9 +46,6 @@ class PluginAddCommand extends core_1.Command {
|
|
|
49
46
|
const nextConfig = store.addPluginToConfig(config, entry);
|
|
50
47
|
configService.writeConfig(nextConfig);
|
|
51
48
|
await store.runOptionalSetup(parsedSpec.name);
|
|
52
|
-
this.log(
|
|
49
|
+
this.log(chalk.green(`Added plugin: ${parsedSpec.name}`));
|
|
53
50
|
}
|
|
54
51
|
}
|
|
55
|
-
PluginAddCommand.description = "Install a plugin package, add it to config, and run its optional setup().";
|
|
56
|
-
PluginAddCommand.strict = false;
|
|
57
|
-
exports.default = PluginAddCommand;
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
const core_1 = require("@oclif/core");
|
|
7
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const config_service_1 = require("../../services/config-service");
|
|
9
|
-
const plugin_store_1 = require("../../services/plugin-store");
|
|
10
|
-
const parse_plugin_spec_1 = require("../../utils/parse-plugin-spec");
|
|
1
|
+
import { Args, Command } from "@oclif/core";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { ConfigService } from "../../services/config-service.js";
|
|
4
|
+
import { PluginStore, normalizePluginConfigEntry } from "../../services/plugin-store.js";
|
|
5
|
+
import { parsePluginSpec } from "../../utils/parse-plugin-spec.js";
|
|
11
6
|
/**
|
|
12
7
|
* Validate one configured plugin can be loaded and instantiated.
|
|
13
8
|
*
|
|
@@ -41,16 +36,23 @@ const runDoctorCheck = async (store, spec) => {
|
|
|
41
36
|
};
|
|
42
37
|
}
|
|
43
38
|
};
|
|
44
|
-
class PluginDoctorCommand extends
|
|
39
|
+
export default class PluginDoctorCommand extends Command {
|
|
40
|
+
static description = "Validate configured plugins are installed and loadable.";
|
|
41
|
+
static args = {
|
|
42
|
+
name: Args.string({
|
|
43
|
+
description: "npm package name (optional; if omitted checks all configured plugins)",
|
|
44
|
+
required: false
|
|
45
|
+
})
|
|
46
|
+
};
|
|
45
47
|
async run() {
|
|
46
48
|
const { args } = await this.parse(PluginDoctorCommand);
|
|
47
|
-
const configService = new
|
|
48
|
-
const store = new
|
|
49
|
+
const configService = new ConfigService();
|
|
50
|
+
const store = new PluginStore(configService);
|
|
49
51
|
configService.ensureConfigFileExists();
|
|
50
52
|
const config = store.readConfig();
|
|
51
|
-
const targetName = args.name ?
|
|
53
|
+
const targetName = args.name ? parsePluginSpec(args.name).name : undefined;
|
|
52
54
|
const configured = config.plugins
|
|
53
|
-
.map((entry) =>
|
|
55
|
+
.map((entry) => normalizePluginConfigEntry(entry))
|
|
54
56
|
.filter((entry) => {
|
|
55
57
|
if (!targetName) {
|
|
56
58
|
return true;
|
|
@@ -59,33 +61,25 @@ class PluginDoctorCommand extends core_1.Command {
|
|
|
59
61
|
});
|
|
60
62
|
if (configured.length === 0) {
|
|
61
63
|
if (targetName) {
|
|
62
|
-
this.log(
|
|
64
|
+
this.log(chalk.gray(`Plugin not found in config: ${targetName}`));
|
|
63
65
|
return;
|
|
64
66
|
}
|
|
65
|
-
this.log(
|
|
67
|
+
this.log(chalk.gray("No plugins configured."));
|
|
66
68
|
return;
|
|
67
69
|
}
|
|
68
70
|
const checks = await Promise.all(configured.map((entry) => runDoctorCheck(store, entry)));
|
|
69
71
|
const failed = checks.filter((item) => !item.installed || !item.loadable);
|
|
70
72
|
checks.forEach((item) => {
|
|
71
73
|
if (item.installed && item.loadable) {
|
|
72
|
-
this.log(
|
|
74
|
+
this.log(chalk.green(`[OK] ${item.plugin}`));
|
|
73
75
|
return;
|
|
74
76
|
}
|
|
75
|
-
this.log(
|
|
77
|
+
this.log(chalk.red(`[FAIL] ${item.plugin}: ${item.error ?? "unknown error"}`));
|
|
76
78
|
});
|
|
77
79
|
if (failed.length === 0) {
|
|
78
|
-
this.log(
|
|
80
|
+
this.log(chalk.green("Plugin doctor checks passed."));
|
|
79
81
|
return;
|
|
80
82
|
}
|
|
81
83
|
throw new Error(`${failed.length} plugin(s) failed doctor checks.`);
|
|
82
84
|
}
|
|
83
85
|
}
|
|
84
|
-
PluginDoctorCommand.description = "Validate configured plugins are installed and loadable.";
|
|
85
|
-
PluginDoctorCommand.args = {
|
|
86
|
-
name: core_1.Args.string({
|
|
87
|
-
description: "npm package name (optional; if omitted checks all configured plugins)",
|
|
88
|
-
required: false
|
|
89
|
-
})
|
|
90
|
-
};
|
|
91
|
-
exports.default = PluginDoctorCommand;
|
|
@@ -1,21 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const config_service_1 = require("../../services/config-service");
|
|
9
|
-
const plugin_store_1 = require("../../services/plugin-store");
|
|
10
|
-
class PluginLsCommand extends core_1.Command {
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { ConfigService } from "../../services/config-service.js";
|
|
4
|
+
import { PluginStore } from "../../services/plugin-store.js";
|
|
5
|
+
export default class PluginLsCommand extends Command {
|
|
6
|
+
static description = "List configured plugins.";
|
|
11
7
|
async run() {
|
|
12
|
-
const configService = new
|
|
13
|
-
const store = new
|
|
8
|
+
const configService = new ConfigService();
|
|
9
|
+
const store = new PluginStore(configService);
|
|
14
10
|
configService.ensureConfigFileExists();
|
|
15
11
|
const config = configService.readConfigOrDefault();
|
|
16
12
|
const entries = config.plugins;
|
|
17
13
|
if (entries.length === 0) {
|
|
18
|
-
this.log(
|
|
14
|
+
this.log(chalk.gray("No plugins configured."));
|
|
19
15
|
return;
|
|
20
16
|
}
|
|
21
17
|
entries
|
|
@@ -28,18 +24,16 @@ class PluginLsCommand extends core_1.Command {
|
|
|
28
24
|
: undefined;
|
|
29
25
|
const meta = store.readInstalledPackageMeta(name);
|
|
30
26
|
if (!meta) {
|
|
31
|
-
const specText = configuredSpec ?
|
|
32
|
-
return `${
|
|
27
|
+
const specText = configuredSpec ? chalk.gray(`(configured: ${configuredSpec})`) : "";
|
|
28
|
+
return `${chalk.yellow(name)} ${chalk.gray("(not installed)")} ${specText}`.trim();
|
|
33
29
|
}
|
|
34
|
-
const specText = configuredSpec ?
|
|
30
|
+
const specText = configuredSpec ? chalk.gray(`(configured: ${configuredSpec})`) : "";
|
|
35
31
|
const desc = meta.description
|
|
36
|
-
?
|
|
37
|
-
:
|
|
38
|
-
const version = meta.version ?
|
|
39
|
-
return `${
|
|
32
|
+
? chalk.gray(meta.description)
|
|
33
|
+
: chalk.gray("(no description)");
|
|
34
|
+
const version = meta.version ? chalk.cyan(`v${meta.version}`) : chalk.gray("(no version)");
|
|
35
|
+
return `${chalk.green(meta.name)} ${version} ${specText} ${desc}`.replace(/\s{2,}/g, " ");
|
|
40
36
|
})
|
|
41
37
|
.forEach((line) => this.log(line));
|
|
42
38
|
}
|
|
43
39
|
}
|
|
44
|
-
PluginLsCommand.description = "List configured plugins.";
|
|
45
|
-
exports.default = PluginLsCommand;
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
const core_1 = require("@oclif/core");
|
|
7
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const config_service_1 = require("../../services/config-service");
|
|
9
|
-
const plugin_installer_1 = require("../../services/plugin-installer");
|
|
10
|
-
const plugin_store_1 = require("../../services/plugin-store");
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { ConfigService } from "../../services/config-service.js";
|
|
4
|
+
import { uninstallPluginPackage } from "../../services/plugin-installer.js";
|
|
5
|
+
import { PluginStore, normalizePluginConfigEntry } from "../../services/plugin-store.js";
|
|
11
6
|
/**
|
|
12
7
|
* Get configured plugin names from config.
|
|
13
8
|
*
|
|
@@ -17,27 +12,26 @@ const plugin_store_1 = require("../../services/plugin-store");
|
|
|
17
12
|
const getConfiguredPluginNames = (store) => {
|
|
18
13
|
const config = store.readConfig();
|
|
19
14
|
const names = config.plugins
|
|
20
|
-
.map((entry) =>
|
|
15
|
+
.map((entry) => normalizePluginConfigEntry(entry).name)
|
|
21
16
|
.filter((name) => name.trim().length > 0);
|
|
22
17
|
return new Set(names);
|
|
23
18
|
};
|
|
24
|
-
class PluginPruneCommand extends
|
|
19
|
+
export default class PluginPruneCommand extends Command {
|
|
20
|
+
static description = "Uninstall plugin packages that are not referenced by config.";
|
|
25
21
|
async run() {
|
|
26
|
-
const configService = new
|
|
27
|
-
const store = new
|
|
22
|
+
const configService = new ConfigService();
|
|
23
|
+
const store = new PluginStore(configService);
|
|
28
24
|
configService.ensureConfigFileExists();
|
|
29
25
|
const configured = getConfiguredPluginNames(store);
|
|
30
26
|
const installed = store.readInstalledDependencyNames();
|
|
31
27
|
const orphans = installed.filter((name) => !configured.has(name));
|
|
32
28
|
if (orphans.length === 0) {
|
|
33
|
-
this.log(
|
|
29
|
+
this.log(chalk.gray("No orphan plugin packages found."));
|
|
34
30
|
return;
|
|
35
31
|
}
|
|
36
32
|
orphans.forEach((name) => {
|
|
37
|
-
|
|
38
|
-
this.log(
|
|
33
|
+
uninstallPluginPackage(store, { packageName: name });
|
|
34
|
+
this.log(chalk.green(`Pruned plugin package: ${name}`));
|
|
39
35
|
});
|
|
40
36
|
}
|
|
41
37
|
}
|
|
42
|
-
PluginPruneCommand.description = "Uninstall plugin packages that are not referenced by config.";
|
|
43
|
-
exports.default = PluginPruneCommand;
|
|
@@ -1,34 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { Args, Command } from "@oclif/core";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { ConfigService } from "../../services/config-service.js";
|
|
4
|
+
import { PluginStore } from "../../services/plugin-store.js";
|
|
5
|
+
export default class PluginRemoveCommand extends Command {
|
|
6
|
+
static description = "Remove a plugin from config (does not uninstall).";
|
|
7
|
+
static args = {
|
|
8
|
+
name: Args.string({
|
|
9
|
+
description: "npm package name",
|
|
10
|
+
required: true
|
|
11
|
+
})
|
|
12
|
+
};
|
|
11
13
|
async run() {
|
|
12
14
|
const { args } = await this.parse(PluginRemoveCommand);
|
|
13
15
|
const name = args.name;
|
|
14
|
-
const configService = new
|
|
15
|
-
const store = new
|
|
16
|
+
const configService = new ConfigService();
|
|
17
|
+
const store = new PluginStore(configService);
|
|
16
18
|
configService.ensureConfigFileExists();
|
|
17
19
|
const config = configService.readConfigOrDefault();
|
|
18
20
|
const out = store.removePluginFromConfig(config, name);
|
|
19
21
|
if (!out.removed) {
|
|
20
|
-
this.log(
|
|
22
|
+
this.log(chalk.gray(`Plugin not found in config: ${name}`));
|
|
21
23
|
return;
|
|
22
24
|
}
|
|
23
25
|
configService.writeConfig(out.config);
|
|
24
|
-
this.log(
|
|
26
|
+
this.log(chalk.green(`Removed from config: ${name}`));
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
|
-
PluginRemoveCommand.description = "Remove a plugin from config (does not uninstall).";
|
|
28
|
-
PluginRemoveCommand.args = {
|
|
29
|
-
name: core_1.Args.string({
|
|
30
|
-
description: "npm package name",
|
|
31
|
-
required: true
|
|
32
|
-
})
|
|
33
|
-
};
|
|
34
|
-
exports.default = PluginRemoveCommand;
|