@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 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: name });
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
- return `${chalk_1.default.yellow(name)} ${chalk_1.default.gray("(not installed)")}`;
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.getConfigDir(), "plugins");
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 v = (0, validate_plugin_default_export_1.validatePluginDefaultExport)({
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 (!v.ok) {
22
- console.error(`Skipping plugin ${spec.name}: ${v.error.message}`);
21
+ if (!validation.ok) {
22
+ console.error(`Skipping plugin ${spec.name}: ${validation.error.message}`);
23
23
  return null;
24
24
  }
25
- return v.plugin;
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
- createPluginsRequire() {
48
- const pkgJsonPath = this.ensurePluginPackageJson();
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.1",
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
- "env-paths": "^2.2.1",
23
- "@pagepocket/lib": "0.8.1",
24
- "@pagepocket/capture-http-cdp-unit": "0.8.1",
25
- "@pagepocket/capture-http-lighterceptor-unit": "0.8.1",
26
- "@pagepocket/capture-http-puppeteer-unit": "0.8.1",
27
- "@pagepocket/build-snapshot-unit": "0.8.1",
28
- "@pagepocket/plugin-yt-dlp": "0.8.1",
29
- "@pagepocket/single-file-unit": "0.8.1",
30
- "@pagepocket/write-down-unit": "0.8.1",
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
  },