@pagepocket/cli 0.8.6 → 0.9.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/README.md +11 -0
- package/dist/commands/plugin/add.js +3 -39
- package/dist/commands/plugin/doctor.js +91 -0
- package/dist/commands/plugin/prune.js +43 -0
- package/dist/commands/plugin/set.js +68 -0
- package/dist/commands/plugin/uninstall.js +55 -0
- package/dist/commands/plugin/update.js +56 -0
- package/dist/commands/view.js +2 -2
- package/dist/services/config-service.js +9 -13
- package/dist/services/load-configured-plugins.js +16 -6
- package/dist/services/plugin-installer.js +55 -5
- package/dist/services/plugin-store.js +85 -22
- package/dist/utils/normalize-argv.js +0 -6
- package/dist/utils/parse-json.js +24 -0
- package/dist/utils/parse-plugin-options.js +48 -0
- package/dist/utils/validate-plugin-default-export.js +16 -22
- package/dist/view.js +2 -2
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -39,7 +39,12 @@ Manage configured plugins:
|
|
|
39
39
|
```bash
|
|
40
40
|
pp plugin ls
|
|
41
41
|
pp plugin add <plugin-name>
|
|
42
|
+
pp plugin set <plugin-name> [...options]
|
|
43
|
+
pp plugin update [plugin-name]
|
|
44
|
+
pp plugin doctor [plugin-name]
|
|
42
45
|
pp plugin remove <plugin-name>
|
|
46
|
+
pp plugin uninstall <plugin-name> [--from-config]
|
|
47
|
+
pp plugin prune
|
|
43
48
|
```
|
|
44
49
|
|
|
45
50
|
Options passing:
|
|
@@ -58,6 +63,12 @@ pp plugin add @scope/plugin --option 1 --option 2
|
|
|
58
63
|
Notes:
|
|
59
64
|
|
|
60
65
|
- `pp plugin remove` only removes the entry from `config.json` (it does not uninstall the package).
|
|
66
|
+
- `pp plugin set` updates options for an already configured plugin.
|
|
67
|
+
- `pp plugin update <plugin-name>` reinstalls that plugin to `latest`.
|
|
68
|
+
- `pp plugin update` reinstalls all configured plugins to `latest`.
|
|
69
|
+
- `pp plugin doctor` checks plugin install/load health and exits non-zero on failures.
|
|
70
|
+
- `pp plugin uninstall <plugin-name>` removes the installed package; add `--from-config` to also remove config.
|
|
71
|
+
- `pp plugin prune` removes installed packages that are no longer referenced in `config.json`.
|
|
61
72
|
- During `pp archive ...`, the CLI reads `config.json` and tries to load all configured plugins.
|
|
62
73
|
If a plugin fails to load, it will be skipped and the archive will continue.
|
|
63
74
|
|
|
@@ -8,46 +8,9 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
8
8
|
const config_service_1 = require("../../services/config-service");
|
|
9
9
|
const plugin_installer_1 = require("../../services/plugin-installer");
|
|
10
10
|
const plugin_store_1 = require("../../services/plugin-store");
|
|
11
|
+
const parse_plugin_options_1 = require("../../utils/parse-plugin-options");
|
|
11
12
|
const parse_plugin_spec_1 = require("../../utils/parse-plugin-spec");
|
|
12
13
|
const validate_plugin_default_export_1 = require("../../utils/validate-plugin-default-export");
|
|
13
|
-
const parseDynamicOptions = (argv) => {
|
|
14
|
-
const tokens = argv.filter(Boolean);
|
|
15
|
-
const pairs = tokens
|
|
16
|
-
.map((t, i) => ({ t, next: tokens[i + 1] }))
|
|
17
|
-
.filter(({ t }) => t === "--option" || t.startsWith("--option-"));
|
|
18
|
-
const object = pairs
|
|
19
|
-
.filter(({ t }) => t.startsWith("--option-"))
|
|
20
|
-
.reduce((acc, { t, next }) => {
|
|
21
|
-
const key = t.slice("--option-".length);
|
|
22
|
-
if (!key) {
|
|
23
|
-
throw new Error("--option-* key is empty");
|
|
24
|
-
}
|
|
25
|
-
if (typeof next === "undefined" || next.startsWith("-")) {
|
|
26
|
-
throw new Error(`${t} expects a value`);
|
|
27
|
-
}
|
|
28
|
-
return { ...acc, [key]: next };
|
|
29
|
-
}, {});
|
|
30
|
-
const arrayValues = pairs
|
|
31
|
-
.filter(({ t }) => t === "--option")
|
|
32
|
-
.map(({ next }) => {
|
|
33
|
-
if (typeof next === "undefined" || next.startsWith("-")) {
|
|
34
|
-
throw new Error("--option expects a value");
|
|
35
|
-
}
|
|
36
|
-
return next;
|
|
37
|
-
});
|
|
38
|
-
const hasObject = Object.keys(object).length > 0;
|
|
39
|
-
const hasArray = arrayValues.length > 0;
|
|
40
|
-
if (hasObject && hasArray) {
|
|
41
|
-
throw new Error("Cannot mix --option with --option-* flags");
|
|
42
|
-
}
|
|
43
|
-
if (hasObject) {
|
|
44
|
-
return { kind: "object", value: object };
|
|
45
|
-
}
|
|
46
|
-
if (hasArray) {
|
|
47
|
-
return { kind: "array", value: arrayValues };
|
|
48
|
-
}
|
|
49
|
-
return { kind: "none" };
|
|
50
|
-
};
|
|
51
14
|
class PluginAddCommand extends core_1.Command {
|
|
52
15
|
async run() {
|
|
53
16
|
const [name, ...rest] = this.argv;
|
|
@@ -67,7 +30,7 @@ class PluginAddCommand extends core_1.Command {
|
|
|
67
30
|
this.log(chalk_1.default.gray(`Plugin already exists in config: ${parsedSpec.name}`));
|
|
68
31
|
return;
|
|
69
32
|
}
|
|
70
|
-
const parsed =
|
|
33
|
+
const parsed = (0, parse_plugin_options_1.parsePluginOptions)(rest);
|
|
71
34
|
const entry = parsed.kind === "none"
|
|
72
35
|
? parsedSpec.name
|
|
73
36
|
: parsed.kind === "array"
|
|
@@ -90,4 +53,5 @@ class PluginAddCommand extends core_1.Command {
|
|
|
90
53
|
}
|
|
91
54
|
}
|
|
92
55
|
PluginAddCommand.description = "Install a plugin package, add it to config, and run its optional setup().";
|
|
56
|
+
PluginAddCommand.strict = false;
|
|
93
57
|
exports.default = PluginAddCommand;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
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");
|
|
11
|
+
/**
|
|
12
|
+
* Validate one configured plugin can be loaded and instantiated.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* const result = await runDoctorCheck(store, spec);
|
|
16
|
+
*/
|
|
17
|
+
const runDoctorCheck = async (store, spec) => {
|
|
18
|
+
const installed = Boolean(store.readInstalledPackageMeta(spec.name));
|
|
19
|
+
if (!installed) {
|
|
20
|
+
return {
|
|
21
|
+
plugin: spec.name,
|
|
22
|
+
installed: false,
|
|
23
|
+
loadable: false,
|
|
24
|
+
error: "not installed"
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
await store.instantiatePluginFromSpec(spec);
|
|
29
|
+
return {
|
|
30
|
+
plugin: spec.name,
|
|
31
|
+
installed: true,
|
|
32
|
+
loadable: true
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return {
|
|
37
|
+
plugin: spec.name,
|
|
38
|
+
installed: true,
|
|
39
|
+
loadable: false,
|
|
40
|
+
error: error instanceof Error ? error.message : String(error)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
class PluginDoctorCommand extends core_1.Command {
|
|
45
|
+
async run() {
|
|
46
|
+
const { args } = await this.parse(PluginDoctorCommand);
|
|
47
|
+
const configService = new config_service_1.ConfigService();
|
|
48
|
+
const store = new plugin_store_1.PluginStore(configService);
|
|
49
|
+
configService.ensureConfigFileExists();
|
|
50
|
+
const config = store.readConfig();
|
|
51
|
+
const targetName = args.name ? (0, parse_plugin_spec_1.parsePluginSpec)(args.name).name : undefined;
|
|
52
|
+
const configured = config.plugins
|
|
53
|
+
.map((entry) => (0, plugin_store_1.normalizePluginConfigEntry)(entry))
|
|
54
|
+
.filter((entry) => {
|
|
55
|
+
if (!targetName) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return entry.name === targetName;
|
|
59
|
+
});
|
|
60
|
+
if (configured.length === 0) {
|
|
61
|
+
if (targetName) {
|
|
62
|
+
this.log(chalk_1.default.gray(`Plugin not found in config: ${targetName}`));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this.log(chalk_1.default.gray("No plugins configured."));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const checks = await Promise.all(configured.map((entry) => runDoctorCheck(store, entry)));
|
|
69
|
+
const failed = checks.filter((item) => !item.installed || !item.loadable);
|
|
70
|
+
checks.forEach((item) => {
|
|
71
|
+
if (item.installed && item.loadable) {
|
|
72
|
+
this.log(chalk_1.default.green(`[OK] ${item.plugin}`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.log(chalk_1.default.red(`[FAIL] ${item.plugin}: ${item.error ?? "unknown error"}`));
|
|
76
|
+
});
|
|
77
|
+
if (failed.length === 0) {
|
|
78
|
+
this.log(chalk_1.default.green("Plugin doctor checks passed."));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
throw new Error(`${failed.length} plugin(s) failed doctor checks.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
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;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
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");
|
|
11
|
+
/**
|
|
12
|
+
* Get configured plugin names from config.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* const names = getConfiguredPluginNames(store);
|
|
16
|
+
*/
|
|
17
|
+
const getConfiguredPluginNames = (store) => {
|
|
18
|
+
const config = store.readConfig();
|
|
19
|
+
const names = config.plugins
|
|
20
|
+
.map((entry) => (0, plugin_store_1.normalizePluginConfigEntry)(entry).name)
|
|
21
|
+
.filter((name) => name.trim().length > 0);
|
|
22
|
+
return new Set(names);
|
|
23
|
+
};
|
|
24
|
+
class PluginPruneCommand extends core_1.Command {
|
|
25
|
+
async run() {
|
|
26
|
+
const configService = new config_service_1.ConfigService();
|
|
27
|
+
const store = new plugin_store_1.PluginStore(configService);
|
|
28
|
+
configService.ensureConfigFileExists();
|
|
29
|
+
const configured = getConfiguredPluginNames(store);
|
|
30
|
+
const installed = store.readInstalledDependencyNames();
|
|
31
|
+
const orphans = installed.filter((name) => !configured.has(name));
|
|
32
|
+
if (orphans.length === 0) {
|
|
33
|
+
this.log(chalk_1.default.gray("No orphan plugin packages found."));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
orphans.forEach((name) => {
|
|
37
|
+
(0, plugin_installer_1.uninstallPluginPackage)(store, { packageName: name });
|
|
38
|
+
this.log(chalk_1.default.green(`Pruned plugin package: ${name}`));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
PluginPruneCommand.description = "Uninstall plugin packages that are not referenced by config.";
|
|
43
|
+
exports.default = PluginPruneCommand;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
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 parse_plugin_options_1 = require("../../utils/parse-plugin-options");
|
|
10
|
+
const parse_plugin_spec_1 = require("../../utils/parse-plugin-spec");
|
|
11
|
+
/**
|
|
12
|
+
* Replace options for a configured plugin and keep its current name/spec.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* const next = buildUpdatedPluginEntry(existingEntry, parsedOptions);
|
|
16
|
+
*/
|
|
17
|
+
const buildUpdatedPluginEntry = (entry, options) => {
|
|
18
|
+
if (typeof entry === "string") {
|
|
19
|
+
return {
|
|
20
|
+
name: entry,
|
|
21
|
+
options
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
name: entry.name,
|
|
26
|
+
...(typeof entry.spec === "string" ? { spec: entry.spec } : {}),
|
|
27
|
+
options
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
class PluginSetCommand extends core_1.Command {
|
|
31
|
+
async run() {
|
|
32
|
+
const [rawName, ...rest] = this.argv;
|
|
33
|
+
if (!rawName) {
|
|
34
|
+
throw new Error("plugin set: missing <plugin-name>");
|
|
35
|
+
}
|
|
36
|
+
const parsedOptions = (0, parse_plugin_options_1.parsePluginOptions)(rest);
|
|
37
|
+
if (parsedOptions.kind === "none") {
|
|
38
|
+
throw new Error("plugin set: provide options with --option or --option-*");
|
|
39
|
+
}
|
|
40
|
+
const targetName = (0, parse_plugin_spec_1.parsePluginSpec)(rawName).name;
|
|
41
|
+
const configService = new config_service_1.ConfigService();
|
|
42
|
+
configService.ensureConfigFileExists();
|
|
43
|
+
const config = configService.readConfigOrDefault();
|
|
44
|
+
const targetIndex = config.plugins.findIndex((entry) => {
|
|
45
|
+
return (typeof entry === "string" ? entry : entry.name) === targetName;
|
|
46
|
+
});
|
|
47
|
+
if (targetIndex < 0) {
|
|
48
|
+
this.log(chalk_1.default.gray(`Plugin not found in config: ${targetName}`));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const targetEntry = config.plugins[targetIndex];
|
|
52
|
+
const nextEntry = buildUpdatedPluginEntry(targetEntry, parsedOptions.value);
|
|
53
|
+
const nextPlugins = config.plugins.map((entry, idx) => {
|
|
54
|
+
if (idx === targetIndex) {
|
|
55
|
+
return nextEntry;
|
|
56
|
+
}
|
|
57
|
+
return entry;
|
|
58
|
+
});
|
|
59
|
+
configService.writeConfig({
|
|
60
|
+
...config,
|
|
61
|
+
plugins: nextPlugins
|
|
62
|
+
});
|
|
63
|
+
this.log(chalk_1.default.green(`Updated plugin options: ${targetName}`));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
PluginSetCommand.description = "Update options for an already configured plugin.";
|
|
67
|
+
PluginSetCommand.strict = false;
|
|
68
|
+
exports.default = PluginSetCommand;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
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");
|
|
11
|
+
const parse_plugin_spec_1 = require("../../utils/parse-plugin-spec");
|
|
12
|
+
class PluginUninstallCommand extends core_1.Command {
|
|
13
|
+
async run() {
|
|
14
|
+
const { args, flags } = await this.parse(PluginUninstallCommand);
|
|
15
|
+
const parsed = (0, parse_plugin_spec_1.parsePluginSpec)(args.name);
|
|
16
|
+
const targetName = parsed.name;
|
|
17
|
+
const configService = new config_service_1.ConfigService();
|
|
18
|
+
const store = new plugin_store_1.PluginStore(configService);
|
|
19
|
+
configService.ensureConfigFileExists();
|
|
20
|
+
const installedMeta = store.readInstalledPackageMeta(targetName);
|
|
21
|
+
if (!installedMeta) {
|
|
22
|
+
this.log(chalk_1.default.gray(`Plugin package is not installed: ${targetName}`));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
(0, plugin_installer_1.uninstallPluginPackage)(store, { packageName: targetName });
|
|
26
|
+
this.log(chalk_1.default.green(`Uninstalled plugin package: ${targetName}`));
|
|
27
|
+
}
|
|
28
|
+
if (!flags.fromConfig) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const config = configService.readConfigOrDefault();
|
|
32
|
+
const out = store.removePluginFromConfig(config, targetName);
|
|
33
|
+
if (!out.removed) {
|
|
34
|
+
this.log(chalk_1.default.gray(`Plugin not found in config: ${targetName}`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
configService.writeConfig(out.config);
|
|
38
|
+
this.log(chalk_1.default.green(`Removed from config: ${targetName}`));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
PluginUninstallCommand.description = "Uninstall a plugin package from the plugin install directory (optional config removal).";
|
|
42
|
+
PluginUninstallCommand.args = {
|
|
43
|
+
name: core_1.Args.string({
|
|
44
|
+
description: "npm package name",
|
|
45
|
+
required: true
|
|
46
|
+
})
|
|
47
|
+
};
|
|
48
|
+
PluginUninstallCommand.flags = {
|
|
49
|
+
fromConfig: core_1.Flags.boolean({
|
|
50
|
+
description: "also remove the plugin from config",
|
|
51
|
+
aliases: ["from-config"],
|
|
52
|
+
default: false
|
|
53
|
+
})
|
|
54
|
+
};
|
|
55
|
+
exports.default = PluginUninstallCommand;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
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");
|
|
11
|
+
const parse_plugin_spec_1 = require("../../utils/parse-plugin-spec");
|
|
12
|
+
/**
|
|
13
|
+
* Read configured plugins and return unique package names.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* const names = getUniqueConfiguredPluginNames(store);
|
|
17
|
+
*/
|
|
18
|
+
const getUniqueConfiguredPluginNames = (store) => {
|
|
19
|
+
const config = store.readConfig();
|
|
20
|
+
return [
|
|
21
|
+
...new Set(config.plugins
|
|
22
|
+
.map((entry) => (0, plugin_store_1.normalizePluginConfigEntry)(entry).name)
|
|
23
|
+
.filter((name) => name.trim().length > 0))
|
|
24
|
+
];
|
|
25
|
+
};
|
|
26
|
+
class PluginUpdateCommand extends core_1.Command {
|
|
27
|
+
async run() {
|
|
28
|
+
const { args } = await this.parse(PluginUpdateCommand);
|
|
29
|
+
const configService = new config_service_1.ConfigService();
|
|
30
|
+
const store = new plugin_store_1.PluginStore(configService);
|
|
31
|
+
configService.ensureConfigFileExists();
|
|
32
|
+
const targetName = args.name ? (0, parse_plugin_spec_1.parsePluginSpec)(args.name).name : undefined;
|
|
33
|
+
const configuredNames = getUniqueConfiguredPluginNames(store);
|
|
34
|
+
if (configuredNames.length === 0) {
|
|
35
|
+
this.log(chalk_1.default.gray("No plugins configured."));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (targetName && !configuredNames.includes(targetName)) {
|
|
39
|
+
this.log(chalk_1.default.gray(`Plugin not found in config: ${targetName}`));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const namesToUpdate = targetName ? [targetName] : configuredNames;
|
|
43
|
+
namesToUpdate.forEach((name) => {
|
|
44
|
+
(0, plugin_installer_1.updatePluginPackageToLatest)(store, { packageName: name });
|
|
45
|
+
this.log(chalk_1.default.green(`Updated plugin: ${name}`));
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
PluginUpdateCommand.description = "Update one plugin (or all configured plugins) to latest.";
|
|
50
|
+
PluginUpdateCommand.args = {
|
|
51
|
+
name: core_1.Args.string({
|
|
52
|
+
description: "npm package name (optional; if omitted updates all configured plugins)",
|
|
53
|
+
required: false
|
|
54
|
+
})
|
|
55
|
+
};
|
|
56
|
+
exports.default = PluginUpdateCommand;
|
package/dist/commands/view.js
CHANGED
|
@@ -14,11 +14,11 @@ class ViewCommand extends core_1.Command {
|
|
|
14
14
|
const { args, flags } = await this.parse(ViewCommand);
|
|
15
15
|
const rootDir = node_path_1.default.resolve(node_process_1.default.cwd(), args.directory);
|
|
16
16
|
const indexPath = node_path_1.default.join(rootDir, "index.html");
|
|
17
|
-
const stat = await node_fs_1.default.promises.stat(rootDir).catch(() =>
|
|
17
|
+
const stat = await node_fs_1.default.promises.stat(rootDir).catch(() => undefined);
|
|
18
18
|
if (!stat || !stat.isDirectory()) {
|
|
19
19
|
throw new Error(`view: directory not found: ${rootDir}`);
|
|
20
20
|
}
|
|
21
|
-
const indexStat = await node_fs_1.default.promises.stat(indexPath).catch(() =>
|
|
21
|
+
const indexStat = await node_fs_1.default.promises.stat(indexPath).catch(() => undefined);
|
|
22
22
|
if (!indexStat || !indexStat.isFile()) {
|
|
23
23
|
throw new Error(`view: index.html not found under: ${rootDir}`);
|
|
24
24
|
}
|
|
@@ -8,6 +8,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
8
8
|
const node_os_1 = __importDefault(require("node:os"));
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
10
|
const env_paths_1 = __importDefault(require("env-paths"));
|
|
11
|
+
const parse_json_1 = require("../utils/parse-json");
|
|
11
12
|
const defaultConfig = {
|
|
12
13
|
plugins: []
|
|
13
14
|
};
|
|
@@ -45,7 +46,7 @@ class ConfigService {
|
|
|
45
46
|
const dir = node_path_1.default.dirname(filePath);
|
|
46
47
|
this.ensureDirExists(dir);
|
|
47
48
|
const tmpPath = `${filePath}.tmp`;
|
|
48
|
-
node_fs_1.default.writeFileSync(tmpPath, `${JSON.stringify(value,
|
|
49
|
+
node_fs_1.default.writeFileSync(tmpPath, `${JSON.stringify(value, undefined, 2)}\n`, "utf8");
|
|
49
50
|
node_fs_1.default.renameSync(tmpPath, filePath);
|
|
50
51
|
}
|
|
51
52
|
isObject(value) {
|
|
@@ -73,33 +74,28 @@ class ConfigService {
|
|
|
73
74
|
...(typeof options === "undefined" ? {} : { options })
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
|
-
return
|
|
77
|
+
return undefined;
|
|
77
78
|
})
|
|
78
|
-
.filter((x) => x !==
|
|
79
|
+
.filter((x) => typeof x !== "undefined");
|
|
79
80
|
return { plugins };
|
|
80
81
|
}
|
|
81
82
|
readConfig() {
|
|
82
83
|
const configPath = this.getConfigPath();
|
|
83
84
|
const rawText = node_fs_1.default.readFileSync(configPath, "utf8");
|
|
84
|
-
const parsed =
|
|
85
|
-
return this.normalizeConfig(parsed);
|
|
85
|
+
const parsed = (0, parse_json_1.parseJson)(rawText);
|
|
86
|
+
return this.normalizeConfig(parsed.ok ? parsed.value : undefined);
|
|
86
87
|
}
|
|
87
88
|
isConfigFilePresent() {
|
|
88
89
|
return node_fs_1.default.existsSync(this.getConfigPath());
|
|
89
90
|
}
|
|
90
91
|
readConfigOrDefault() {
|
|
91
92
|
const configPath = this.getConfigPath();
|
|
92
|
-
const text = node_fs_1.default.existsSync(configPath) ? node_fs_1.default.readFileSync(configPath, "utf8") :
|
|
93
|
+
const text = node_fs_1.default.existsSync(configPath) ? node_fs_1.default.readFileSync(configPath, "utf8") : undefined;
|
|
93
94
|
if (!text) {
|
|
94
95
|
return defaultConfig;
|
|
95
96
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return this.normalizeConfig(parsed);
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
return defaultConfig;
|
|
102
|
-
}
|
|
97
|
+
const parsed = (0, parse_json_1.parseJson)(text);
|
|
98
|
+
return parsed.ok ? this.normalizeConfig(parsed.value) : defaultConfig;
|
|
103
99
|
}
|
|
104
100
|
writeConfig(config) {
|
|
105
101
|
this.writeJsonAtomic(this.getConfigPath(), this.normalizeConfig(config));
|
|
@@ -5,6 +5,18 @@ const validate_plugin_default_export_1 = require("../utils/validate-plugin-defau
|
|
|
5
5
|
const config_service_1 = require("./config-service");
|
|
6
6
|
const plugin_store_1 = require("./plugin-store");
|
|
7
7
|
const loadConfiguredPlugins = async () => {
|
|
8
|
+
const getErrorMessage = (error) => {
|
|
9
|
+
if (error instanceof Error) {
|
|
10
|
+
return error.message;
|
|
11
|
+
}
|
|
12
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
13
|
+
const msg = error.message;
|
|
14
|
+
if (typeof msg === "string") {
|
|
15
|
+
return msg;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return String(error);
|
|
19
|
+
};
|
|
8
20
|
const configService = new config_service_1.ConfigService();
|
|
9
21
|
const store = new plugin_store_1.PluginStore(configService);
|
|
10
22
|
configService.ensureConfigFileExists();
|
|
@@ -20,18 +32,16 @@ const loadConfiguredPlugins = async () => {
|
|
|
20
32
|
});
|
|
21
33
|
if (!validation.ok) {
|
|
22
34
|
console.error(`Skipping plugin ${spec.name}: ${validation.error.message}`);
|
|
23
|
-
return
|
|
35
|
+
return undefined;
|
|
24
36
|
}
|
|
25
37
|
return validation.plugin;
|
|
26
38
|
}
|
|
27
39
|
catch (e) {
|
|
28
|
-
const msg = e
|
|
29
|
-
? e.message
|
|
30
|
-
: String(e);
|
|
40
|
+
const msg = getErrorMessage(e);
|
|
31
41
|
console.error(`Failed to load plugin ${spec.name}: ${msg}`);
|
|
32
|
-
return
|
|
42
|
+
return undefined;
|
|
33
43
|
}
|
|
34
44
|
}));
|
|
35
|
-
return loaded.filter((x) => x !==
|
|
45
|
+
return loaded.filter((x) => typeof x !== "undefined");
|
|
36
46
|
};
|
|
37
47
|
exports.loadConfiguredPlugins = loadConfiguredPlugins;
|
|
@@ -1,8 +1,58 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.installPluginPackage = void 0;
|
|
3
|
+
exports.uninstallPluginPackage = exports.updatePluginPackageToLatest = exports.installPluginPackage = void 0;
|
|
4
4
|
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
/**
|
|
6
|
+
* Install a package spec with pnpm first, then fallback to npm.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* tryInstallWithFallback("/path/to/plugins", "@scope/plugin@latest");
|
|
10
|
+
*/
|
|
11
|
+
const tryInstallWithFallback = (installDir, packageSpec) => {
|
|
12
|
+
const tryRun = (cmd, args) => {
|
|
13
|
+
const r = (0, node_child_process_1.spawnSync)(cmd, args, {
|
|
14
|
+
cwd: installDir,
|
|
15
|
+
stdio: "inherit",
|
|
16
|
+
shell: false
|
|
17
|
+
});
|
|
18
|
+
if (r.error) {
|
|
19
|
+
return { ok: false, error: r.error };
|
|
20
|
+
}
|
|
21
|
+
if (typeof r.status === "number" && r.status !== 0) {
|
|
22
|
+
return { ok: false, error: new Error(`${cmd} exited with code ${r.status}`) };
|
|
23
|
+
}
|
|
24
|
+
return { ok: true };
|
|
25
|
+
};
|
|
26
|
+
const pnpm = tryRun("pnpm", ["add", "--silent", packageSpec]);
|
|
27
|
+
if (pnpm.ok) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const npm = tryRun("npm", ["install", "--silent", packageSpec]);
|
|
31
|
+
if (npm.ok) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Failed to install ${packageSpec}. Ensure pnpm or npm is available.`);
|
|
35
|
+
};
|
|
5
36
|
const installPluginPackage = (store, input) => {
|
|
37
|
+
store.ensurePluginPackageJson();
|
|
38
|
+
const installDir = store.getInstallDir();
|
|
39
|
+
tryInstallWithFallback(installDir, input.packageName);
|
|
40
|
+
};
|
|
41
|
+
exports.installPluginPackage = installPluginPackage;
|
|
42
|
+
const updatePluginPackageToLatest = (store, input) => {
|
|
43
|
+
store.ensurePluginPackageJson();
|
|
44
|
+
const installDir = store.getInstallDir();
|
|
45
|
+
const packageSpec = `${input.packageName}@latest`;
|
|
46
|
+
tryInstallWithFallback(installDir, packageSpec);
|
|
47
|
+
};
|
|
48
|
+
exports.updatePluginPackageToLatest = updatePluginPackageToLatest;
|
|
49
|
+
/**
|
|
50
|
+
* Uninstall a package name with pnpm first, then fallback to npm.
|
|
51
|
+
*
|
|
52
|
+
* Usage:
|
|
53
|
+
* uninstallPluginPackage(store, { packageName: "@scope/plugin" });
|
|
54
|
+
*/
|
|
55
|
+
const uninstallPluginPackage = (store, input) => {
|
|
6
56
|
store.ensurePluginPackageJson();
|
|
7
57
|
const installDir = store.getInstallDir();
|
|
8
58
|
const tryRun = (cmd, args) => {
|
|
@@ -19,14 +69,14 @@ const installPluginPackage = (store, input) => {
|
|
|
19
69
|
}
|
|
20
70
|
return { ok: true };
|
|
21
71
|
};
|
|
22
|
-
const pnpm = tryRun("pnpm", ["
|
|
72
|
+
const pnpm = tryRun("pnpm", ["remove", "--silent", input.packageName]);
|
|
23
73
|
if (pnpm.ok) {
|
|
24
74
|
return;
|
|
25
75
|
}
|
|
26
|
-
const npm = tryRun("npm", ["
|
|
76
|
+
const npm = tryRun("npm", ["uninstall", "--silent", input.packageName]);
|
|
27
77
|
if (npm.ok) {
|
|
28
78
|
return;
|
|
29
79
|
}
|
|
30
|
-
throw new Error(`Failed to
|
|
80
|
+
throw new Error(`Failed to uninstall ${input.packageName}. Ensure pnpm or npm is available.`);
|
|
31
81
|
};
|
|
32
|
-
exports.
|
|
82
|
+
exports.uninstallPluginPackage = uninstallPluginPackage;
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -8,6 +41,8 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
8
41
|
const node_module_1 = require("node:module");
|
|
9
42
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
43
|
const node_url_1 = require("node:url");
|
|
44
|
+
const parse_json_1 = require("../utils/parse-json");
|
|
45
|
+
const validate_plugin_default_export_1 = require("../utils/validate-plugin-default-export");
|
|
11
46
|
const normalizePluginConfigEntry = (entry) => {
|
|
12
47
|
if (typeof entry === "string") {
|
|
13
48
|
return { name: entry };
|
|
@@ -21,6 +56,18 @@ const normalizePluginConfigEntry = (entry) => {
|
|
|
21
56
|
exports.normalizePluginConfigEntry = normalizePluginConfigEntry;
|
|
22
57
|
const getPluginNameFromSpec = (spec) => spec.name;
|
|
23
58
|
exports.getPluginNameFromSpec = getPluginNameFromSpec;
|
|
59
|
+
const isRecord = (value) => {
|
|
60
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
61
|
+
};
|
|
62
|
+
const isCallable = (value) => {
|
|
63
|
+
return typeof value === "function";
|
|
64
|
+
};
|
|
65
|
+
const isStringRecord = (value) => {
|
|
66
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return Object.values(value).every((v) => typeof v === "string");
|
|
70
|
+
};
|
|
24
71
|
class PluginStore {
|
|
25
72
|
constructor(configService) {
|
|
26
73
|
this.configService = configService;
|
|
@@ -42,7 +89,7 @@ class PluginStore {
|
|
|
42
89
|
private: true,
|
|
43
90
|
version: "0.0.0"
|
|
44
91
|
};
|
|
45
|
-
node_fs_1.default.writeFileSync(pkgJsonPath, `${JSON.stringify(pkgJson,
|
|
92
|
+
node_fs_1.default.writeFileSync(pkgJsonPath, `${JSON.stringify(pkgJson, undefined, 2)}\n`, "utf8");
|
|
46
93
|
return pkgJsonPath;
|
|
47
94
|
}
|
|
48
95
|
createPluginsRequireFromDir(dirPath) {
|
|
@@ -53,7 +100,7 @@ class PluginStore {
|
|
|
53
100
|
private: true,
|
|
54
101
|
version: "0.0.0"
|
|
55
102
|
};
|
|
56
|
-
node_fs_1.default.writeFileSync(pkgJsonPath, `${JSON.stringify(pkgJson,
|
|
103
|
+
node_fs_1.default.writeFileSync(pkgJsonPath, `${JSON.stringify(pkgJson, undefined, 2)}\n`, "utf8");
|
|
57
104
|
}
|
|
58
105
|
return (0, node_module_1.createRequire)(pkgJsonPath);
|
|
59
106
|
}
|
|
@@ -69,11 +116,11 @@ class PluginStore {
|
|
|
69
116
|
try {
|
|
70
117
|
const pkgJsonPath = this.resolveInstalledPackageJsonPath(pluginName);
|
|
71
118
|
const text = node_fs_1.default.readFileSync(pkgJsonPath, "utf8");
|
|
72
|
-
const json =
|
|
73
|
-
if (!json || typeof json !== "object") {
|
|
119
|
+
const json = (0, parse_json_1.parseJson)(text);
|
|
120
|
+
if (!json.ok || !json.value || typeof json.value !== "object") {
|
|
74
121
|
return { name: pluginName };
|
|
75
122
|
}
|
|
76
|
-
const o = json;
|
|
123
|
+
const o = json.value;
|
|
77
124
|
return {
|
|
78
125
|
name: typeof o.name === "string" ? o.name : pluginName,
|
|
79
126
|
description: typeof o.description === "string" ? o.description : undefined,
|
|
@@ -81,8 +128,28 @@ class PluginStore {
|
|
|
81
128
|
};
|
|
82
129
|
}
|
|
83
130
|
catch {
|
|
84
|
-
return
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Read top-level dependencies from plugins install `package.json`.
|
|
136
|
+
*
|
|
137
|
+
* Usage:
|
|
138
|
+
* const names = store.readInstalledDependencyNames();
|
|
139
|
+
*/
|
|
140
|
+
readInstalledDependencyNames() {
|
|
141
|
+
const pkgJsonPath = this.ensurePluginPackageJson();
|
|
142
|
+
const text = node_fs_1.default.readFileSync(pkgJsonPath, "utf8");
|
|
143
|
+
const parsed = (0, parse_json_1.parseJson)(text);
|
|
144
|
+
if (!parsed.ok || !parsed.value || typeof parsed.value !== "object") {
|
|
145
|
+
return [];
|
|
85
146
|
}
|
|
147
|
+
const record = parsed.value;
|
|
148
|
+
const dependencies = record.dependencies;
|
|
149
|
+
if (!isStringRecord(dependencies)) {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
return Object.keys(dependencies);
|
|
86
153
|
}
|
|
87
154
|
async importPluginModule(pluginName) {
|
|
88
155
|
const req = this.createPluginsRequire();
|
|
@@ -90,30 +157,26 @@ class PluginStore {
|
|
|
90
157
|
return this.importResolvedPath(resolved);
|
|
91
158
|
}
|
|
92
159
|
async importResolvedPath(resolved) {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
return (mod ?? {});
|
|
160
|
+
const mod = await Promise.resolve(`${(0, node_url_1.pathToFileURL)(resolved).href}`).then(s => __importStar(require(s)));
|
|
161
|
+
return isRecord(mod) ? mod : {};
|
|
96
162
|
}
|
|
97
163
|
async instantiatePluginFromSpec(spec) {
|
|
98
164
|
const mod = await this.importPluginModule(spec.name);
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
if (Array.isArray(spec.options)) {
|
|
108
|
-
return new ctor(...spec.options);
|
|
165
|
+
const result = (0, validate_plugin_default_export_1.validatePluginDefaultExport)({
|
|
166
|
+
pluginName: spec.name,
|
|
167
|
+
moduleExports: mod,
|
|
168
|
+
options: spec.options
|
|
169
|
+
});
|
|
170
|
+
if (!result.ok) {
|
|
171
|
+
throw result.error;
|
|
109
172
|
}
|
|
110
|
-
return
|
|
173
|
+
return result.plugin;
|
|
111
174
|
}
|
|
112
175
|
async runOptionalSetup(pluginName) {
|
|
113
176
|
const mod = await this.importPluginModule(pluginName);
|
|
114
177
|
const setup = mod.setup;
|
|
115
|
-
if (
|
|
116
|
-
await setup();
|
|
178
|
+
if (isCallable(setup)) {
|
|
179
|
+
await Promise.resolve(setup());
|
|
117
180
|
}
|
|
118
181
|
}
|
|
119
182
|
readConfig() {
|
|
@@ -21,12 +21,6 @@ const normalizeArgv = (argv) => {
|
|
|
21
21
|
return argv;
|
|
22
22
|
}
|
|
23
23
|
const first = argv[0];
|
|
24
|
-
if (first === "plugin") {
|
|
25
|
-
const sub = argv[1];
|
|
26
|
-
if (sub === "ls" || sub === "add" || sub === "remove") {
|
|
27
|
-
return [`plugin:${sub}`, ...argv.slice(2)];
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
24
|
// If the first token is a URL, treat it as implicit `archive`.
|
|
31
25
|
if (first && (0, exports.isUrlLike)(first)) {
|
|
32
26
|
return ["archive", ...argv];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseJson = void 0;
|
|
4
|
+
const toErrorMessage = (value) => {
|
|
5
|
+
if (value instanceof Error) {
|
|
6
|
+
return value.message;
|
|
7
|
+
}
|
|
8
|
+
if (typeof value === "string") {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
if (value && typeof value === "object" && "message" in value) {
|
|
12
|
+
return String(value.message);
|
|
13
|
+
}
|
|
14
|
+
return JSON.stringify(value);
|
|
15
|
+
};
|
|
16
|
+
const parseJson = (text) => {
|
|
17
|
+
try {
|
|
18
|
+
return { ok: true, value: JSON.parse(text) };
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
return { ok: false, error: new Error(toErrorMessage(e)) };
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
exports.parseJson = parseJson;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parsePluginOptions = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Parse plugin options from argv tokens.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const parsed = parsePluginOptions(["--option-mode", "strict"]);
|
|
9
|
+
*/
|
|
10
|
+
const parsePluginOptions = (argv) => {
|
|
11
|
+
const tokens = argv.filter(Boolean);
|
|
12
|
+
const pairs = tokens
|
|
13
|
+
.map((t, i) => ({ t, next: tokens[i + 1] }))
|
|
14
|
+
.filter(({ t }) => t === "--option" || t.startsWith("--option-"));
|
|
15
|
+
const object = pairs
|
|
16
|
+
.filter(({ t }) => t.startsWith("--option-"))
|
|
17
|
+
.reduce((acc, { t, next }) => {
|
|
18
|
+
const key = t.slice("--option-".length);
|
|
19
|
+
if (!key) {
|
|
20
|
+
throw new Error("--option-* key is empty");
|
|
21
|
+
}
|
|
22
|
+
if (typeof next === "undefined" || next.startsWith("-")) {
|
|
23
|
+
throw new Error(`${t} expects a value`);
|
|
24
|
+
}
|
|
25
|
+
return { ...acc, [key]: next };
|
|
26
|
+
}, {});
|
|
27
|
+
const arrayValues = pairs
|
|
28
|
+
.filter(({ t }) => t === "--option")
|
|
29
|
+
.map(({ next }) => {
|
|
30
|
+
if (typeof next === "undefined" || next.startsWith("-")) {
|
|
31
|
+
throw new Error("--option expects a value");
|
|
32
|
+
}
|
|
33
|
+
return next;
|
|
34
|
+
});
|
|
35
|
+
const hasObject = Object.keys(object).length > 0;
|
|
36
|
+
const hasArray = arrayValues.length > 0;
|
|
37
|
+
if (hasObject && hasArray) {
|
|
38
|
+
throw new Error("Cannot mix --option with --option-* flags");
|
|
39
|
+
}
|
|
40
|
+
if (hasObject) {
|
|
41
|
+
return { kind: "object", value: object };
|
|
42
|
+
}
|
|
43
|
+
if (hasArray) {
|
|
44
|
+
return { kind: "array", value: arrayValues };
|
|
45
|
+
}
|
|
46
|
+
return { kind: "none" };
|
|
47
|
+
};
|
|
48
|
+
exports.parsePluginOptions = parsePluginOptions;
|
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validatePluginDefaultExport = void 0;
|
|
4
4
|
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
const isPagePocketPlugin = (value) => {
|
|
6
|
+
if (!isRecord(value)) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
if (typeof value.name !== "string" || value.name.trim().length === 0) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (typeof value.setup !== "function") {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (typeof value.contribute !== "undefined" && typeof value.contribute !== "function") {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
};
|
|
5
20
|
const toError = (value) => {
|
|
6
21
|
if (value instanceof Error) {
|
|
7
22
|
return value;
|
|
@@ -24,33 +39,12 @@ const validatePluginDefaultExport = (input) => {
|
|
|
24
39
|
: Array.isArray(options)
|
|
25
40
|
? new ctor(...options)
|
|
26
41
|
: new ctor(options);
|
|
27
|
-
if (!
|
|
42
|
+
if (!isPagePocketPlugin(instance)) {
|
|
28
43
|
return {
|
|
29
44
|
ok: false,
|
|
30
45
|
error: new Error(`Plugin ${pluginName} default export did not construct an object.`)
|
|
31
46
|
};
|
|
32
47
|
}
|
|
33
|
-
const name = instance.name;
|
|
34
|
-
if (typeof name !== "string" || name.trim().length === 0) {
|
|
35
|
-
return {
|
|
36
|
-
ok: false,
|
|
37
|
-
error: new Error(`Plugin ${pluginName} instance must have a non-empty string 'name'.`)
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
const setup = instance.setup;
|
|
41
|
-
if (typeof setup !== "function") {
|
|
42
|
-
return {
|
|
43
|
-
ok: false,
|
|
44
|
-
error: new Error(`Plugin ${pluginName} instance must implement setup(host).`)
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const contribute = instance.contribute;
|
|
48
|
-
if (typeof contribute !== "undefined" && typeof contribute !== "function") {
|
|
49
|
-
return {
|
|
50
|
-
ok: false,
|
|
51
|
-
error: new Error(`Plugin ${pluginName} contribute must be a function if provided.`)
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
48
|
return { ok: true, plugin: instance };
|
|
55
49
|
}
|
|
56
50
|
catch (e) {
|
package/dist/view.js
CHANGED
|
@@ -11,11 +11,11 @@ const koa_send_1 = __importDefault(require("koa-send"));
|
|
|
11
11
|
const koa_static_1 = __importDefault(require("koa-static"));
|
|
12
12
|
const createViewServer = async (args) => {
|
|
13
13
|
const indexPath = node_path_1.default.join(args.rootDir, "index.html");
|
|
14
|
-
const stat = await node_fs_1.default.promises.stat(args.rootDir).catch(() =>
|
|
14
|
+
const stat = await node_fs_1.default.promises.stat(args.rootDir).catch(() => undefined);
|
|
15
15
|
if (!stat || !stat.isDirectory()) {
|
|
16
16
|
throw new Error(`view: directory not found: ${args.rootDir}`);
|
|
17
17
|
}
|
|
18
|
-
const indexStat = await node_fs_1.default.promises.stat(indexPath).catch(() =>
|
|
18
|
+
const indexStat = await node_fs_1.default.promises.stat(indexPath).catch(() => undefined);
|
|
19
19
|
if (!indexStat || !indexStat.isFile()) {
|
|
20
20
|
throw new Error(`view: index.html not found under: ${args.rootDir}`);
|
|
21
21
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagepocket/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "CLI for capturing offline snapshots of web pages.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
"koa-static": "^5.0.0",
|
|
22
22
|
"npm-package-arg": "^13.0.2",
|
|
23
23
|
"ora": "^9.0.0",
|
|
24
|
-
"@pagepocket/build-snapshot-unit": "0.
|
|
25
|
-
"@pagepocket/capture-http-
|
|
26
|
-
"@pagepocket/capture-http-lighterceptor-unit": "0.
|
|
27
|
-
"@pagepocket/capture-http-
|
|
28
|
-
"@pagepocket/
|
|
29
|
-
"@pagepocket/
|
|
30
|
-
"@pagepocket/plugin-yt-dlp": "0.
|
|
31
|
-
"@pagepocket/single-file-unit": "0.
|
|
32
|
-
"@pagepocket/write-down-unit": "0.
|
|
24
|
+
"@pagepocket/build-snapshot-unit": "0.9.1",
|
|
25
|
+
"@pagepocket/capture-http-puppeteer-unit": "0.9.1",
|
|
26
|
+
"@pagepocket/capture-http-lighterceptor-unit": "0.9.1",
|
|
27
|
+
"@pagepocket/capture-http-cdp-unit": "0.9.1",
|
|
28
|
+
"@pagepocket/lib": "0.9.1",
|
|
29
|
+
"@pagepocket/contracts": "0.9.1",
|
|
30
|
+
"@pagepocket/plugin-yt-dlp": "0.9.1",
|
|
31
|
+
"@pagepocket/single-file-unit": "0.9.1",
|
|
32
|
+
"@pagepocket/write-down-unit": "0.9.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/koa": "^2.15.0",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"oclif": {
|
|
43
43
|
"bin": "pp",
|
|
44
|
+
"topicSeparator": " ",
|
|
44
45
|
"commands": {
|
|
45
46
|
"strategy": "pattern",
|
|
46
47
|
"target": "dist/commands"
|