@pagepocket/cli 0.8.1 → 0.8.2
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 +6 -0
- package/dist/commands/plugin/add.js +12 -10
- package/dist/commands/plugin/ls.js +9 -2
- package/dist/services/config-service.js +7 -1
- package/dist/services/load-configured-plugins.js +5 -5
- package/dist/services/plugin-store.js +18 -2
- package/dist/utils/parse-plugin-spec.js +16 -0
- package/package.json +13 -12
package/README.md
CHANGED
|
@@ -61,6 +61,12 @@ Notes:
|
|
|
61
61
|
- During `pp archive ...`, the CLI reads `config.json` and tries to load all configured plugins.
|
|
62
62
|
If a plugin fails to load, it will be skipped and the archive will continue.
|
|
63
63
|
|
|
64
|
+
Plugin packages are installed to your user data directory (not the config directory):
|
|
65
|
+
|
|
66
|
+
- Linux: `~/.local/share/pagepocket/plugins/`
|
|
67
|
+
- macOS: `~/Library/Application Support/pagepocket/plugins/`
|
|
68
|
+
- Windows: `%APPDATA%\\pagepocket\\plugins\\`
|
|
69
|
+
|
|
64
70
|
## Output
|
|
65
71
|
|
|
66
72
|
Snapshots are written to a folder named after the page title inside the output
|
|
@@ -8,6 +8,7 @@ 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_spec_1 = require("../../utils/parse-plugin-spec");
|
|
11
12
|
const validate_plugin_default_export_1 = require("../../utils/validate-plugin-default-export");
|
|
12
13
|
const parseDynamicOptions = (argv) => {
|
|
13
14
|
const tokens = argv.filter(Boolean);
|
|
@@ -57,24 +58,25 @@ class PluginAddCommand extends core_1.Command {
|
|
|
57
58
|
this.log("Usage: pp plugin add <plugin-name> [--option-* <value> ...] [--option <value> ...]");
|
|
58
59
|
return;
|
|
59
60
|
}
|
|
61
|
+
const parsedSpec = (0, parse_plugin_spec_1.parsePluginSpec)(name);
|
|
60
62
|
const configService = new config_service_1.ConfigService();
|
|
61
63
|
const store = new plugin_store_1.PluginStore(configService);
|
|
62
64
|
configService.ensureConfigFileExists();
|
|
63
65
|
const config = configService.readConfigOrDefault();
|
|
64
|
-
if (store.hasPlugin(config, name)) {
|
|
65
|
-
this.log(chalk_1.default.gray(`Plugin already exists in config: ${name}`));
|
|
66
|
+
if (store.hasPlugin(config, parsedSpec.name)) {
|
|
67
|
+
this.log(chalk_1.default.gray(`Plugin already exists in config: ${parsedSpec.name}`));
|
|
66
68
|
return;
|
|
67
69
|
}
|
|
68
70
|
const parsed = parseDynamicOptions(rest);
|
|
69
71
|
const entry = parsed.kind === "none"
|
|
70
|
-
? name
|
|
72
|
+
? parsedSpec.name
|
|
71
73
|
: parsed.kind === "array"
|
|
72
|
-
? { name, options: parsed.value }
|
|
73
|
-
: { name, options: parsed.value };
|
|
74
|
-
(0, plugin_installer_1.installPluginPackage)(store, { packageName:
|
|
75
|
-
const mod = await store.importPluginModule(name);
|
|
74
|
+
? { name: parsedSpec.name, spec: parsedSpec.spec, options: parsed.value }
|
|
75
|
+
: { name: parsedSpec.name, spec: parsedSpec.spec, options: parsed.value };
|
|
76
|
+
(0, plugin_installer_1.installPluginPackage)(store, { packageName: parsedSpec.spec });
|
|
77
|
+
const mod = await store.importPluginModule(parsedSpec.name);
|
|
76
78
|
const validation = (0, validate_plugin_default_export_1.validatePluginDefaultExport)({
|
|
77
|
-
pluginName: name,
|
|
79
|
+
pluginName: parsedSpec.name,
|
|
78
80
|
moduleExports: mod,
|
|
79
81
|
options: parsed.kind === "none" ? undefined : parsed.value
|
|
80
82
|
});
|
|
@@ -83,8 +85,8 @@ class PluginAddCommand extends core_1.Command {
|
|
|
83
85
|
}
|
|
84
86
|
const nextConfig = store.addPluginToConfig(config, entry);
|
|
85
87
|
configService.writeConfig(nextConfig);
|
|
86
|
-
await store.runOptionalSetup(name);
|
|
87
|
-
this.log(chalk_1.default.green(`Added plugin: ${name}`));
|
|
88
|
+
await store.runOptionalSetup(parsedSpec.name);
|
|
89
|
+
this.log(chalk_1.default.green(`Added plugin: ${parsedSpec.name}`));
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
PluginAddCommand.description = "Install a plugin package, add it to config, and run its optional setup().";
|
|
@@ -21,15 +21,22 @@ class PluginLsCommand extends core_1.Command {
|
|
|
21
21
|
entries
|
|
22
22
|
.map((entry) => {
|
|
23
23
|
const name = typeof entry === "string" ? entry : entry.name;
|
|
24
|
+
const configuredSpec = typeof entry === "string"
|
|
25
|
+
? undefined
|
|
26
|
+
: typeof entry.spec === "string" && entry.spec.trim().length > 0
|
|
27
|
+
? entry.spec
|
|
28
|
+
: undefined;
|
|
24
29
|
const meta = store.readInstalledPackageMeta(name);
|
|
25
30
|
if (!meta) {
|
|
26
|
-
|
|
31
|
+
const specText = configuredSpec ? chalk_1.default.gray(`(configured: ${configuredSpec})`) : "";
|
|
32
|
+
return `${chalk_1.default.yellow(name)} ${chalk_1.default.gray("(not installed)")} ${specText}`.trim();
|
|
27
33
|
}
|
|
34
|
+
const specText = configuredSpec ? chalk_1.default.gray(`(configured: ${configuredSpec})`) : "";
|
|
28
35
|
const desc = meta.description
|
|
29
36
|
? chalk_1.default.gray(meta.description)
|
|
30
37
|
: chalk_1.default.gray("(no description)");
|
|
31
38
|
const version = meta.version ? chalk_1.default.cyan(`v${meta.version}`) : chalk_1.default.gray("(no version)");
|
|
32
|
-
return `${chalk_1.default.green(meta.name)} ${version} ${desc}
|
|
39
|
+
return `${chalk_1.default.green(meta.name)} ${version} ${specText} ${desc}`.replace(/\s{2,}/g, " ");
|
|
33
40
|
})
|
|
34
41
|
.forEach((line) => this.log(line));
|
|
35
42
|
}
|
|
@@ -25,11 +25,15 @@ class ConfigService {
|
|
|
25
25
|
const base = xdgConfigHome && xdgConfigHome.trim() ? xdgConfigHome : node_path_1.default.join(homeDir, ".config");
|
|
26
26
|
return node_path_1.default.join(base, this.appName);
|
|
27
27
|
}
|
|
28
|
+
getDataDir() {
|
|
29
|
+
const p = (0, env_paths_1.default)(this.appName, { suffix: "" });
|
|
30
|
+
return p.data;
|
|
31
|
+
}
|
|
28
32
|
getConfigPath() {
|
|
29
33
|
return node_path_1.default.join(this.getConfigDir(), "config.json");
|
|
30
34
|
}
|
|
31
35
|
getPluginsInstallDir() {
|
|
32
|
-
return node_path_1.default.join(this.
|
|
36
|
+
return node_path_1.default.join(this.getDataDir(), "plugins");
|
|
33
37
|
}
|
|
34
38
|
ensureConfigDirExists() {
|
|
35
39
|
this.ensureDirExists(this.getConfigDir());
|
|
@@ -61,9 +65,11 @@ class ConfigService {
|
|
|
61
65
|
return entry;
|
|
62
66
|
}
|
|
63
67
|
if (this.isObject(entry) && typeof entry.name === "string") {
|
|
68
|
+
const spec = "spec" in entry && typeof entry.spec === "string" ? entry.spec : undefined;
|
|
64
69
|
const options = "options" in entry ? entry.options : undefined;
|
|
65
70
|
return {
|
|
66
71
|
name: entry.name,
|
|
72
|
+
...(typeof spec === "undefined" ? {} : { spec }),
|
|
67
73
|
...(typeof options === "undefined" ? {} : { options })
|
|
68
74
|
};
|
|
69
75
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.loadConfiguredPlugins = void 0;
|
|
4
|
+
const validate_plugin_default_export_1 = require("../utils/validate-plugin-default-export");
|
|
4
5
|
const config_service_1 = require("./config-service");
|
|
5
6
|
const plugin_store_1 = require("./plugin-store");
|
|
6
|
-
const validate_plugin_default_export_1 = require("../utils/validate-plugin-default-export");
|
|
7
7
|
const loadConfiguredPlugins = async () => {
|
|
8
8
|
const configService = new config_service_1.ConfigService();
|
|
9
9
|
const store = new plugin_store_1.PluginStore(configService);
|
|
@@ -13,16 +13,16 @@ const loadConfiguredPlugins = async () => {
|
|
|
13
13
|
const loaded = await Promise.all(specs.map(async (spec) => {
|
|
14
14
|
try {
|
|
15
15
|
const mod = await store.importPluginModule(spec.name);
|
|
16
|
-
const
|
|
16
|
+
const validation = (0, validate_plugin_default_export_1.validatePluginDefaultExport)({
|
|
17
17
|
pluginName: spec.name,
|
|
18
18
|
moduleExports: mod,
|
|
19
19
|
options: spec.options
|
|
20
20
|
});
|
|
21
|
-
if (!
|
|
22
|
-
console.error(`Skipping plugin ${spec.name}: ${
|
|
21
|
+
if (!validation.ok) {
|
|
22
|
+
console.error(`Skipping plugin ${spec.name}: ${validation.error.message}`);
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
|
-
return
|
|
25
|
+
return validation.plugin;
|
|
26
26
|
}
|
|
27
27
|
catch (e) {
|
|
28
28
|
const msg = e && typeof e.message === "string"
|
|
@@ -14,6 +14,7 @@ const normalizePluginConfigEntry = (entry) => {
|
|
|
14
14
|
}
|
|
15
15
|
return {
|
|
16
16
|
name: entry.name,
|
|
17
|
+
...(typeof entry.spec === "string" ? { spec: entry.spec } : {}),
|
|
17
18
|
...(typeof entry.options === "undefined" ? {} : { options: entry.options })
|
|
18
19
|
};
|
|
19
20
|
};
|
|
@@ -44,10 +45,22 @@ class PluginStore {
|
|
|
44
45
|
node_fs_1.default.writeFileSync(pkgJsonPath, `${JSON.stringify(pkgJson, null, 2)}\n`, "utf8");
|
|
45
46
|
return pkgJsonPath;
|
|
46
47
|
}
|
|
47
|
-
|
|
48
|
-
const pkgJsonPath =
|
|
48
|
+
createPluginsRequireFromDir(dirPath) {
|
|
49
|
+
const pkgJsonPath = node_path_1.default.join(dirPath, "package.json");
|
|
50
|
+
if (!node_fs_1.default.existsSync(pkgJsonPath)) {
|
|
51
|
+
const pkgJson = {
|
|
52
|
+
name: "pagepocket-user-plugins",
|
|
53
|
+
private: true,
|
|
54
|
+
version: "0.0.0"
|
|
55
|
+
};
|
|
56
|
+
node_fs_1.default.writeFileSync(pkgJsonPath, `${JSON.stringify(pkgJson, null, 2)}\n`, "utf8");
|
|
57
|
+
}
|
|
49
58
|
return (0, node_module_1.createRequire)(pkgJsonPath);
|
|
50
59
|
}
|
|
60
|
+
createPluginsRequire() {
|
|
61
|
+
this.ensurePluginPackageJson();
|
|
62
|
+
return this.createPluginsRequireFromDir(this.getInstallDir());
|
|
63
|
+
}
|
|
51
64
|
resolveInstalledPackageJsonPath(pluginName) {
|
|
52
65
|
const req = this.createPluginsRequire();
|
|
53
66
|
return req.resolve(`${pluginName}/package.json`);
|
|
@@ -74,6 +87,9 @@ class PluginStore {
|
|
|
74
87
|
async importPluginModule(pluginName) {
|
|
75
88
|
const req = this.createPluginsRequire();
|
|
76
89
|
const resolved = req.resolve(pluginName);
|
|
90
|
+
return this.importResolvedPath(resolved);
|
|
91
|
+
}
|
|
92
|
+
async importResolvedPath(resolved) {
|
|
77
93
|
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
78
94
|
const mod = await dynamicImport((0, node_url_1.pathToFileURL)(resolved).href);
|
|
79
95
|
return (mod ?? {});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parsePluginSpec = void 0;
|
|
4
|
+
const parsePluginSpec = (spec) => {
|
|
5
|
+
const trimmed = spec.trim();
|
|
6
|
+
if (!trimmed) {
|
|
7
|
+
throw new Error("plugin spec is empty");
|
|
8
|
+
}
|
|
9
|
+
const npa = require("npm-package-arg");
|
|
10
|
+
const parsed = npa(trimmed);
|
|
11
|
+
if (!parsed || typeof parsed.name !== "string" || !parsed.name) {
|
|
12
|
+
throw new Error(`Invalid plugin spec: ${trimmed}`);
|
|
13
|
+
}
|
|
14
|
+
return { name: parsed.name, spec: trimmed };
|
|
15
|
+
};
|
|
16
|
+
exports.parsePluginSpec = parsePluginSpec;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagepocket/cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "CLI for capturing offline snapshots of web pages.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,26 +15,27 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@oclif/core": "^4.0.9",
|
|
17
17
|
"chalk": "^4.1.2",
|
|
18
|
+
"env-paths": "^2.2.1",
|
|
18
19
|
"koa": "^2.16.2",
|
|
19
20
|
"koa-send": "^5.0.1",
|
|
20
21
|
"koa-static": "^5.0.0",
|
|
22
|
+
"npm-package-arg": "^13.0.2",
|
|
21
23
|
"ora": "^9.0.0",
|
|
22
|
-
"
|
|
23
|
-
"@pagepocket/
|
|
24
|
-
"@pagepocket/capture-http-cdp-unit": "0.8.
|
|
25
|
-
"@pagepocket/
|
|
26
|
-
"@pagepocket/
|
|
27
|
-
"@pagepocket/
|
|
28
|
-
"@pagepocket/plugin-yt-dlp": "0.8.
|
|
29
|
-
"@pagepocket/single-file-unit": "0.8.
|
|
30
|
-
"@pagepocket/write-down-unit": "0.8.
|
|
31
|
-
"@pagepocket/contracts": "0.8.1"
|
|
24
|
+
"@pagepocket/build-snapshot-unit": "0.8.2",
|
|
25
|
+
"@pagepocket/capture-http-puppeteer-unit": "0.8.2",
|
|
26
|
+
"@pagepocket/capture-http-cdp-unit": "0.8.2",
|
|
27
|
+
"@pagepocket/contracts": "0.8.2",
|
|
28
|
+
"@pagepocket/lib": "0.8.2",
|
|
29
|
+
"@pagepocket/capture-http-lighterceptor-unit": "0.8.2",
|
|
30
|
+
"@pagepocket/plugin-yt-dlp": "0.8.2",
|
|
31
|
+
"@pagepocket/single-file-unit": "0.8.2",
|
|
32
|
+
"@pagepocket/write-down-unit": "0.8.2"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^20.11.30",
|
|
35
35
|
"@types/koa": "^2.15.0",
|
|
36
36
|
"@types/koa-send": "^4.1.5",
|
|
37
37
|
"@types/koa-static": "^4.0.4",
|
|
38
|
+
"@types/node": "^20.11.30",
|
|
38
39
|
"tsx": "^4.19.3",
|
|
39
40
|
"typescript": "^5.4.5"
|
|
40
41
|
},
|