@pagepocket/cli 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,14 +3,14 @@ import { pathToFileURL } from "node:url";
3
3
  import { Args, Command, Flags } from "@oclif/core";
4
4
  import { ga, ns, PagePocket } from "@pagepocket/lib";
5
5
  import chalk from "chalk";
6
- import { loadConfiguredPlugins } from "../services/load-configured-plugins.js";
7
6
  import { ConfigService } from "../services/config-service.js";
7
+ import { loadConfiguredPlugins } from "../services/load-configured-plugins.js";
8
8
  import { readBuiltinStrategy, listBuiltinStrategyNames } from "../services/strategy/builtin-strategy-registry.js";
9
- import { StrategyService } from "../services/strategy/strategy-service.js";
10
9
  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";
10
+ import { StrategyService } from "../services/strategy/strategy-service.js";
13
11
  import { UnitStore } from "../services/units/unit-store.js";
12
+ import { isUnitLike, resolveUnitConstructor } from "../services/units/unit-validate.js";
13
+ import { parsePinnedSpec } from "../services/user-packages/parse-pinned-spec.js";
14
14
  import { uniq } from "../utils/array.js";
15
15
  import { withSpinner } from "../utils/with-spinner.js";
16
16
  const buildMissingStrategyError = (input) => {
@@ -46,15 +46,15 @@ const resolveStrategy = (input) => {
46
46
  };
47
47
  const assertNoDuplicateUnitIds = (input) => {
48
48
  const seen = new Set();
49
- const dup = input.units.find((u) => {
50
- if (seen.has(u.id)) {
49
+ const duplicateUnit = input.units.find((unit) => {
50
+ if (seen.has(unit.id)) {
51
51
  return true;
52
52
  }
53
- seen.add(u.id);
53
+ seen.add(unit.id);
54
54
  return false;
55
55
  });
56
- if (dup) {
57
- throw new Error(`Duplicate unit id detected in strategy ${input.strategyName}: ${dup.id}`);
56
+ if (duplicateUnit) {
57
+ throw new Error(`Duplicate unit id detected in strategy ${input.strategyName}: ${duplicateUnit.id}`);
58
58
  }
59
59
  };
60
60
  const loadInstalledStrategyUnits = async (input) => {
@@ -64,8 +64,8 @@ const loadInstalledStrategyUnits = async (input) => {
64
64
  const strategyFile = strategyService.readStrategy(input.strategyName);
65
65
  const normalized = normalizeStrategyUnits(strategyFile);
66
66
  const installed = unitStore.readInstalledDependencyVersions();
67
- const drift = normalized.flatMap((u) => {
68
- const pinned = parsePinnedSpec(u.ref);
67
+ const drift = normalized.flatMap((unit) => {
68
+ const pinned = parsePinnedSpec(unit.ref);
69
69
  const installedVersion = installed[pinned.name];
70
70
  if (!installedVersion || installedVersion !== pinned.version) {
71
71
  return [
@@ -80,15 +80,15 @@ const loadInstalledStrategyUnits = async (input) => {
80
80
  });
81
81
  if (drift.length > 0) {
82
82
  const details = drift
83
- .map((d) => {
84
- const installedText = typeof d.installed === "string" ? d.installed : "(not installed)";
85
- return `${d.name}: pinned ${d.pinned}, installed ${installedText}`;
83
+ .map((driftItem) => {
84
+ const installedText = typeof driftItem.installed === "string" ? driftItem.installed : "(not installed)";
85
+ return `${driftItem.name}: pinned ${driftItem.pinned}, installed ${installedText}`;
86
86
  })
87
87
  .join("; ");
88
88
  throw new Error(`Strategy drift detected (${input.strategyName}). ${details}. ` +
89
89
  `Fix: pp strategy update ${input.strategyName} OR pp strategy pin ${input.strategyName}`);
90
90
  }
91
- const units = await Promise.all(normalized.map(async (u) => unitStore.instantiateFromRef(u.ref, u.args)));
91
+ const units = await Promise.all(normalized.map(async (unit) => unitStore.instantiateFromRef(unit.ref, unit.args)));
92
92
  assertNoDuplicateUnitIds({
93
93
  strategyName: input.strategyName,
94
94
  units
@@ -112,9 +112,9 @@ const instantiateBuiltinUnit = async (input) => {
112
112
  };
113
113
  const loadBuiltinStrategyUnits = async (input) => {
114
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
115
+ const units = await Promise.all(normalized.map(async (unit) => instantiateBuiltinUnit({
116
+ ref: unit.ref,
117
+ args: unit.args
118
118
  })));
119
119
  assertNoDuplicateUnitIds({
120
120
  strategyName: input.strategyName,
@@ -38,9 +38,7 @@ export default class PluginSetCommand extends Command {
38
38
  const configService = new ConfigService();
39
39
  configService.ensureConfigFileExists();
40
40
  const config = configService.readConfigOrDefault();
41
- const targetIndex = config.plugins.findIndex((entry) => {
42
- return (typeof entry === "string" ? entry : entry.name) === targetName;
43
- });
41
+ const targetIndex = config.plugins.findIndex((entry) => (typeof entry === "string" ? entry : entry.name) === targetName);
44
42
  if (targetIndex < 0) {
45
43
  this.log(chalk.gray(`Plugin not found in config: ${targetName}`));
46
44
  return;
@@ -6,15 +6,15 @@ export default class StrategyDoctorCommand extends Command {
6
6
  async run() {
7
7
  const service = new StrategyService();
8
8
  const report = service.doctor();
9
- report.conflicts.forEach((c) => {
10
- this.log(chalk.red(`[CONFLICT] ${c.name}`));
11
- Object.entries(c.versions).forEach(([version, strategies]) => {
9
+ report.conflicts.forEach((conflict) => {
10
+ this.log(chalk.red(`[CONFLICT] ${conflict.name}`));
11
+ Object.entries(conflict.versions).forEach(([version, strategies]) => {
12
12
  this.log(chalk.red(` ${version}: ${strategies.join(", ")}`));
13
13
  });
14
14
  });
15
- report.drift.forEach((d) => {
16
- this.log(chalk.yellow(`[DRIFT] ${d.strategy}`));
17
- d.items.forEach((item) => {
15
+ report.drift.forEach((drift) => {
16
+ this.log(chalk.yellow(`[DRIFT] ${drift.strategy}`));
17
+ drift.items.forEach((item) => {
18
18
  const installed = typeof item.installed === "string" ? item.installed : "(not installed)";
19
19
  this.log(chalk.yellow(` ${item.name}: pinned ${item.pinned}, installed ${installed}`));
20
20
  });
@@ -1,22 +1,39 @@
1
1
  import { Command } from "@oclif/core";
2
- import { listBuiltinStrategyNames } from "../../services/strategy/builtin-strategy-registry.js";
2
+ import chalk from "chalk";
3
+ import { listBuiltinStrategyNames, readBuiltinStrategy } from "../../services/strategy/builtin-strategy-registry.js";
4
+ import { normalizeStrategyUnits } from "../../services/strategy/strategy-normalize.js";
3
5
  import { StrategyService } from "../../services/strategy/strategy-service.js";
4
6
  import { uniq } from "../../utils/array.js";
7
+ const formatStrategy = (name, strategyFile) => {
8
+ const units = normalizeStrategyUnits(strategyFile);
9
+ const pipeline = units.map((unit) => unit.ref).join(" -> ");
10
+ return pipeline.length > 0 ? `${name}${chalk.dim(`(${pipeline})`)}` : name;
11
+ };
5
12
  export default class StrategyLsCommand extends Command {
6
13
  static description = "List available strategies.";
7
14
  async run() {
8
15
  const service = new StrategyService();
9
16
  service.ensureConfigFileExists();
10
- const builtin = listBuiltinStrategyNames();
11
- const installed = service.listInstalledStrategyNames();
12
- const names = uniq([...builtin, ...installed])
13
- .map((n) => n.trim())
14
- .filter((n) => n.length > 0)
15
- .sort((a, b) => a.localeCompare(b));
17
+ const builtinNames = listBuiltinStrategyNames();
18
+ const installedNames = service.listInstalledStrategyNames();
19
+ const names = uniq([...builtinNames, ...installedNames])
20
+ .map((name) => name.trim())
21
+ .filter((name) => name.length > 0)
22
+ .sort((left, right) => left.localeCompare(right));
16
23
  if (names.length === 0) {
17
24
  this.log("No strategies available.");
18
25
  return;
19
26
  }
20
- names.forEach((n) => this.log(n));
27
+ for (const name of names) {
28
+ const strategyFile = installedNames.includes(name)
29
+ ? service.readStrategy(name)
30
+ : readBuiltinStrategy(name);
31
+ if (strategyFile) {
32
+ this.log(formatStrategy(name, strategyFile));
33
+ }
34
+ else {
35
+ this.log(name);
36
+ }
37
+ }
21
38
  }
22
39
  }
@@ -15,8 +15,8 @@ export class ConfigService {
15
15
  }
16
16
  getConfigDir() {
17
17
  if (process.platform === "win32") {
18
- const p = envPaths(this.appName, { suffix: "" });
19
- return p.config;
18
+ const envPathsResult = envPaths(this.appName, { suffix: "" });
19
+ return envPathsResult.config;
20
20
  }
21
21
  const homeDir = os.homedir();
22
22
  const xdgConfigHome = process.env.XDG_CONFIG_HOME;
@@ -24,8 +24,8 @@ export class ConfigService {
24
24
  return path.join(base, this.appName);
25
25
  }
26
26
  getDataDir() {
27
- const p = envPaths(this.appName, { suffix: "" });
28
- return p.data;
27
+ const envPathsResult = envPaths(this.appName, { suffix: "" });
28
+ return envPathsResult.data;
29
29
  }
30
30
  getConfigPath() {
31
31
  return path.join(this.getConfigDir(), "config.json");
@@ -73,13 +73,13 @@ export class ConfigService {
73
73
  }
74
74
  return undefined;
75
75
  })
76
- .filter((x) => typeof x !== "undefined")
76
+ .filter((pluginEntry) => typeof pluginEntry !== "undefined")
77
77
  : [];
78
78
  const strategiesRaw = value.strategies;
79
79
  const strategies = Array.isArray(strategiesRaw)
80
80
  ? strategiesRaw
81
- .map((x) => (typeof x === "string" ? x.trim() : ""))
82
- .filter((x) => x.length > 0)
81
+ .map((strategyName) => (typeof strategyName === "string" ? strategyName.trim() : ""))
82
+ .filter((strategyName) => strategyName.length > 0)
83
83
  : [];
84
84
  const packsRaw = value.strategyPacks;
85
85
  const strategyPacks = Array.isArray(packsRaw)
@@ -98,7 +98,7 @@ export class ConfigService {
98
98
  }
99
99
  return undefined;
100
100
  })
101
- .filter((x) => Boolean(x))
101
+ .filter((strategyPackEntry) => Boolean(strategyPackEntry))
102
102
  : [];
103
103
  return {
104
104
  plugins,
@@ -7,16 +7,16 @@ import { spawnSync } from "node:child_process";
7
7
  */
8
8
  const tryInstallWithFallback = (installDir, packageSpec) => {
9
9
  const tryRun = (cmd, args) => {
10
- const r = spawnSync(cmd, args, {
10
+ const result = spawnSync(cmd, args, {
11
11
  cwd: installDir,
12
12
  stdio: "inherit",
13
13
  shell: false
14
14
  });
15
- if (r.error) {
16
- return { ok: false, error: r.error };
15
+ if (result.error) {
16
+ return { ok: false, error: result.error };
17
17
  }
18
- if (typeof r.status === "number" && r.status !== 0) {
19
- return { ok: false, error: new Error(`${cmd} exited with code ${r.status}`) };
18
+ if (typeof result.status === "number" && result.status !== 0) {
19
+ return { ok: false, error: new Error(`${cmd} exited with code ${result.status}`) };
20
20
  }
21
21
  return { ok: true };
22
22
  };
@@ -51,16 +51,16 @@ export const uninstallPluginPackage = (store, input) => {
51
51
  store.ensurePluginPackageJson();
52
52
  const installDir = store.getInstallDir();
53
53
  const tryRun = (cmd, args) => {
54
- const r = spawnSync(cmd, args, {
54
+ const result = spawnSync(cmd, args, {
55
55
  cwd: installDir,
56
56
  stdio: "inherit",
57
57
  shell: false
58
58
  });
59
- if (r.error) {
60
- return { ok: false, error: r.error };
59
+ if (result.error) {
60
+ return { ok: false, error: result.error };
61
61
  }
62
- if (typeof r.status === "number" && r.status !== 0) {
63
- return { ok: false, error: new Error(`${cmd} exited with code ${r.status}`) };
62
+ if (typeof result.status === "number" && result.status !== 0) {
63
+ return { ok: false, error: new Error(`${cmd} exited with code ${result.status}`) };
64
64
  }
65
65
  return { ok: true };
66
66
  };
@@ -15,17 +15,13 @@ export const normalizePluginConfigEntry = (entry) => {
15
15
  };
16
16
  };
17
17
  export const getPluginNameFromSpec = (spec) => spec.name;
18
- const isRecord = (value) => {
19
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
20
- };
21
- const isCallable = (value) => {
22
- return typeof value === "function";
23
- };
18
+ const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
19
+ const isCallable = (value) => typeof value === "function";
24
20
  const isStringRecord = (value) => {
25
21
  if (!value || typeof value !== "object" || Array.isArray(value)) {
26
22
  return false;
27
23
  }
28
- return Object.values(value).every((v) => typeof v === "string");
24
+ return Object.values(value).every((entryValue) => typeof entryValue === "string");
29
25
  };
30
26
  export class PluginStore {
31
27
  configService;
@@ -80,11 +76,13 @@ export class PluginStore {
80
76
  if (!json.ok || !json.value || typeof json.value !== "object") {
81
77
  return { name: pluginName };
82
78
  }
83
- const o = json.value;
79
+ const packageJsonRecord = json.value;
84
80
  return {
85
- name: typeof o.name === "string" ? o.name : pluginName,
86
- description: typeof o.description === "string" ? o.description : undefined,
87
- version: typeof o.version === "string" ? o.version : undefined
81
+ name: typeof packageJsonRecord.name === "string" ? packageJsonRecord.name : pluginName,
82
+ description: typeof packageJsonRecord.description === "string"
83
+ ? packageJsonRecord.description
84
+ : undefined,
85
+ version: typeof packageJsonRecord.version === "string" ? packageJsonRecord.version : undefined
88
86
  };
89
87
  }
90
88
  catch {
@@ -146,7 +144,7 @@ export class PluginStore {
146
144
  this.configService.writeConfig(next);
147
145
  }
148
146
  hasPlugin(config, pluginName) {
149
- return config.plugins.some((e) => getPluginNameFromSpec(normalizePluginConfigEntry(e)) === pluginName);
147
+ return config.plugins.some((pluginEntry) => getPluginNameFromSpec(normalizePluginConfigEntry(pluginEntry)) === pluginName);
150
148
  }
151
149
  addPluginToConfig(config, entry) {
152
150
  const spec = normalizePluginConfigEntry(entry);
@@ -157,7 +155,7 @@ export class PluginStore {
157
155
  }
158
156
  removePluginFromConfig(config, pluginName) {
159
157
  const before = config.plugins;
160
- const after = before.filter((e) => getPluginNameFromSpec(normalizePluginConfigEntry(e)) !== pluginName);
158
+ const after = before.filter((pluginEntry) => getPluginNameFromSpec(normalizePluginConfigEntry(pluginEntry)) !== pluginName);
161
159
  return {
162
160
  config: { ...config, plugins: after },
163
161
  removed: after.length !== before.length
@@ -1,23 +1,14 @@
1
- import path from "node:path";
2
1
  import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ import { MemoryCache } from "@pagepocket/shared";
3
4
  import { readStrategiesFromPackRoot } from "./strategy-pack-read.js";
4
- const readBuiltinStrategiesUnsafe = () => {
5
- const req = createRequire(import.meta.url);
6
- const pkgJsonPath = req.resolve("@pagepocket/builtin-strategy/package.json");
7
- const packRoot = path.dirname(pkgJsonPath);
8
- return readStrategiesFromPackRoot(packRoot);
9
- };
10
- export const readBuiltinStrategies = () => {
11
- try {
12
- return readBuiltinStrategiesUnsafe();
13
- }
14
- catch {
15
- return [];
16
- }
17
- };
18
- export const listBuiltinStrategyNames = () => {
19
- return readBuiltinStrategies().map((strategy) => strategy.name);
20
- };
21
- export const readBuiltinStrategy = (name) => {
22
- return readBuiltinStrategies().find((strategy) => strategy.name === name);
23
- };
5
+ const BUILTIN_KEY = "builtin";
6
+ const cache = new MemoryCache();
7
+ const resolveBuiltinPackRoot = () => {
8
+ const require = createRequire(import.meta.url);
9
+ const packageJsonPath = require.resolve("@pagepocket/builtin-strategy/package.json");
10
+ return path.dirname(packageJsonPath);
11
+ };
12
+ const loadBuiltinStrategies = () => cache.getOrSet(BUILTIN_KEY, () => readStrategiesFromPackRoot(resolveBuiltinPackRoot()));
13
+ export const listBuiltinStrategyNames = () => loadBuiltinStrategies().map((strategy) => strategy.name);
14
+ export const readBuiltinStrategy = (name) => loadBuiltinStrategies().find((strategy) => strategy.name === name);
@@ -1,12 +1,12 @@
1
1
  import { parsePinnedSpec } from "../user-packages/parse-pinned-spec.js";
2
2
  export const collectWantedVersions = (strategies) => {
3
3
  const wanted = {};
4
- for (const s of strategies) {
5
- for (const u of s.units) {
6
- const pinned = parsePinnedSpec(u.ref);
4
+ for (const strategy of strategies) {
5
+ for (const unit of strategy.units) {
6
+ const pinned = parsePinnedSpec(unit.ref);
7
7
  wanted[pinned.name] ??= {};
8
8
  wanted[pinned.name][pinned.version] ??= [];
9
- wanted[pinned.name][pinned.version].push(s.name);
9
+ wanted[pinned.name][pinned.version].push(strategy.name);
10
10
  }
11
11
  }
12
12
  return wanted;
@@ -23,8 +23,8 @@ export const computeConflicts = (wanted) => {
23
23
  return conflicts;
24
24
  };
25
25
  export const computeDrift = (input) => {
26
- const items = input.units.flatMap((u) => {
27
- const pinned = parsePinnedSpec(u.ref);
26
+ const items = input.units.flatMap((unit) => {
27
+ const pinned = parsePinnedSpec(unit.ref);
28
28
  const installedVersion = input.installed[pinned.name];
29
29
  if (!installedVersion || installedVersion !== pinned.version) {
30
30
  return [
@@ -1,7 +1,7 @@
1
1
  import { uniq } from "../../utils/array.js";
2
- export const listStrategyNamesFromConfig = (config) => {
3
- return uniq((config.strategies ?? []).map((s) => s.trim()).filter((s) => s.length > 0));
4
- };
2
+ export const listStrategyNamesFromConfig = (config) => uniq((config.strategies ?? [])
3
+ .map((strategyName) => strategyName.trim())
4
+ .filter((strategyName) => strategyName.length > 0));
5
5
  export const requireStrategyInstalled = (config, name) => {
6
6
  const installed = listStrategyNamesFromConfig(config);
7
7
  if (!installed.includes(name)) {
@@ -13,6 +13,6 @@ export const withStrategyInConfig = (config, name) => {
13
13
  return { ...config, strategies };
14
14
  };
15
15
  export const withoutStrategyInConfig = (config, name) => {
16
- const strategies = (config.strategies ?? []).filter((s) => s !== name);
16
+ const strategies = (config.strategies ?? []).filter((strategyName) => strategyName !== name);
17
17
  return { ...config, strategies };
18
18
  };
@@ -1,10 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { parseJson } from "../../utils/parse-json.js";
4
3
  import { isUrlLike } from "../../utils/normalize-argv.js";
5
- const isRecord = (value) => {
6
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7
- };
4
+ import { parseJson } from "../../utils/parse-json.js";
5
+ const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
8
6
  const isStrategyFile = (value) => {
9
7
  if (!isRecord(value)) {
10
8
  return false;
@@ -1,9 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { parseJson } from "../../utils/parse-json.js";
4
- const isRecord = (value) => {
5
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
6
- };
4
+ const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
7
5
  const isStrategyFile = (value) => {
8
6
  if (!isRecord(value)) {
9
7
  return false;
@@ -23,12 +21,8 @@ const isStrategyFile = (value) => {
23
21
  }
24
22
  return true;
25
23
  };
26
- export const getStrategiesDir = (configService) => {
27
- return path.join(configService.getConfigDir(), "strategies");
28
- };
29
- export const getStrategyPath = (configService, name) => {
30
- return path.join(getStrategiesDir(configService), `${name}.json`);
31
- };
24
+ export const getStrategiesDir = (configService) => path.join(configService.getConfigDir(), "strategies");
25
+ export const getStrategyPath = (configService, name) => path.join(getStrategiesDir(configService), `${name}.json`);
32
26
  export const readStrategyFile = (filePath) => {
33
27
  const text = fs.readFileSync(filePath, "utf8");
34
28
  const parsed = parseJson(text);
@@ -1,20 +1,18 @@
1
1
  import { parsePinnedSpec } from "../user-packages/parse-pinned-spec.js";
2
- const isRecord = (value) => {
3
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4
- };
2
+ const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
5
3
  const isJsonValue = (value) => {
6
4
  if (value === null) {
7
5
  return true;
8
6
  }
9
- const t = typeof value;
10
- if (t === "string" || t === "number" || t === "boolean") {
7
+ const valueType = typeof value;
8
+ if (valueType === "string" || valueType === "number" || valueType === "boolean") {
11
9
  return true;
12
10
  }
13
11
  if (Array.isArray(value)) {
14
- return value.every((v) => isJsonValue(v));
12
+ return value.every((item) => isJsonValue(item));
15
13
  }
16
14
  if (isRecord(value)) {
17
- return Object.values(value).every((v) => isJsonValue(v));
15
+ return Object.values(value).every((entryValue) => isJsonValue(entryValue));
18
16
  }
19
17
  return false;
20
18
  };
@@ -1,9 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { parseJson } from "../../utils/parse-json.js";
4
- const isRecord = (value) => {
5
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
6
- };
4
+ const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
7
5
  const isStrategyFile = (value) => {
8
6
  if (!isRecord(value)) {
9
7
  return false;
@@ -30,7 +28,7 @@ export const readStrategiesFromPackRoot = (packRoot) => {
30
28
  }
31
29
  const fileNames = fs
32
30
  .readdirSync(strategiesDir)
33
- .filter((f) => f.endsWith(".strategy.json"))
31
+ .filter((fileName) => fileName.endsWith(".strategy.json"))
34
32
  .sort((a, b) => a.localeCompare(b));
35
33
  return fileNames.map((fileName) => {
36
34
  const filePath = path.join(strategiesDir, fileName);
@@ -1,7 +1,7 @@
1
1
  import path from "node:path";
2
+ import { parsePinnedSpec } from "../user-packages/parse-pinned-spec.js";
2
3
  import { installPinnedPackage } from "../user-packages/user-package-installer.js";
3
4
  import { updatePackageToLatest } from "../user-packages/user-package-installer.js";
4
- import { parsePinnedSpec } from "../user-packages/parse-pinned-spec.js";
5
5
  import { UserPackageStore } from "../user-packages/user-package-store.js";
6
6
  const STRATEGY_PACKS_KIND = "strategy-packs";
7
7
  const STRATEGY_PACKS_PACKAGE_JSON_NAME = "pagepocket-user-strategy-packs";
@@ -1,8 +1,8 @@
1
1
  import fs from "node:fs";
2
+ import { uniq } from "../../utils/array.js";
2
3
  import { ConfigService } from "../config-service.js";
3
- import { parsePinnedSpec } from "../user-packages/parse-pinned-spec.js";
4
4
  import { UnitStore } from "../units/unit-store.js";
5
- import { uniq } from "../../utils/array.js";
5
+ import { parsePinnedSpec } from "../user-packages/parse-pinned-spec.js";
6
6
  import { collectWantedVersions, computeConflicts, computeDrift, ensureNoInstalledVersionConflicts } from "./strategy-analyze.js";
7
7
  import { listStrategyNamesFromConfig, requireStrategyInstalled, withStrategyInConfig, withoutStrategyInConfig } from "./strategy-config.js";
8
8
  import { fetchStrategyFile } from "./strategy-fetch.js";
@@ -55,8 +55,8 @@ export class StrategyService {
55
55
  if (strategies.length === 0) {
56
56
  throw new Error(`No *.strategy.json found in package: ${pinned.spec}`);
57
57
  }
58
- const allUnits = strategies.flatMap((s) => normalizeStrategyUnits(s));
59
- const refs = uniq(allUnits.map((u) => u.ref));
58
+ const allUnits = strategies.flatMap((strategy) => normalizeStrategyUnits(strategy));
59
+ const refs = uniq(allUnits.map((unit) => unit.ref));
60
60
  ensureNoInstalledVersionConflicts(this.unitStore.readInstalledDependencyVersions(), refs);
61
61
  refs.forEach((ref) => this.unitStore.installPinned(ref));
62
62
  const installedStrategies = [];
@@ -73,13 +73,13 @@ export class StrategyService {
73
73
  writeJsonAtomic(strategyPath, toWrite);
74
74
  installedStrategies.push(name);
75
75
  });
76
- const nextConfig = installedStrategies.reduce((acc, s) => withStrategyInConfig(acc, s), config);
76
+ const nextConfig = installedStrategies.reduce((configAccumulator, strategyName) => withStrategyInConfig(configAccumulator, strategyName), config);
77
77
  const existing = nextConfig.strategyPacks ?? [];
78
- const filtered = existing.filter((x) => {
79
- if (typeof x === "string") {
80
- return x.trim().length > 0;
78
+ const filtered = existing.filter((packEntry) => {
79
+ if (typeof packEntry === "string") {
80
+ return packEntry.trim().length > 0;
81
81
  }
82
- return x.name !== pinned.name;
82
+ return packEntry.name !== pinned.name;
83
83
  });
84
84
  const strategyPacks = [
85
85
  ...filtered,
@@ -93,7 +93,7 @@ export class StrategyService {
93
93
  }
94
94
  const { strategy, source } = await fetchStrategyFile(sourceTrimmed);
95
95
  const normalizedUnits = normalizeStrategyUnits(strategy);
96
- const refs = uniq(normalizedUnits.map((u) => u.ref));
96
+ const refs = uniq(normalizedUnits.map((unit) => unit.ref));
97
97
  const strategyPath = this.getStrategyPath(strategy.name);
98
98
  if (!input.force && fs.existsSync(strategyPath)) {
99
99
  throw new Error(`Strategy already exists: ${strategy.name}`);
@@ -128,15 +128,15 @@ export class StrategyService {
128
128
  const installed = this.unitStore.readInstalledDependencyVersions();
129
129
  if (opts?.packageOnly) {
130
130
  const packageNames = uniq(names
131
- .flatMap((n) => normalizeStrategyUnits(this.readStrategy(n)).map((u) => parsePinnedSpec(u.ref).name))
132
- .filter((x) => x.trim().length > 0));
131
+ .flatMap((strategyName) => normalizeStrategyUnits(this.readStrategy(strategyName)).map((unit) => parsePinnedSpec(unit.ref).name))
132
+ .filter((packageName) => packageName.trim().length > 0));
133
133
  packageNames.forEach((pkg) => {
134
134
  this.unitStore.updateToLatest(pkg);
135
135
  });
136
136
  const afterInstalled = this.unitStore.readInstalledDependencyVersions();
137
- const afterStrategies = listStrategyNamesFromConfig(config).map((n) => ({
138
- name: n,
139
- units: normalizeStrategyUnits(this.readStrategy(n))
137
+ const afterStrategies = listStrategyNamesFromConfig(config).map((strategyName) => ({
138
+ name: strategyName,
139
+ units: normalizeStrategyUnits(this.readStrategy(strategyName))
140
140
  }));
141
141
  const wanted = collectWantedVersions(afterStrategies);
142
142
  const conflicts = computeConflicts(wanted);
@@ -144,8 +144,12 @@ export class StrategyService {
144
144
  throw new Error(`Strategy version conflicts detected (${conflicts.length}). Run 'pp strategy doctor' for details.`);
145
145
  }
146
146
  const drift = afterStrategies
147
- .map((s) => computeDrift({ strategyName: s.name, units: s.units, installed: afterInstalled }))
148
- .filter((d) => d.items.length > 0);
147
+ .map((strategy) => computeDrift({
148
+ strategyName: strategy.name,
149
+ units: strategy.units,
150
+ installed: afterInstalled
151
+ }))
152
+ .filter((drift) => drift.items.length > 0);
149
153
  if (drift.length > 0) {
150
154
  throw new Error("Strategy drift detected after --package-only update. Run 'pp strategy doctor'.");
151
155
  }
@@ -153,23 +157,23 @@ export class StrategyService {
153
157
  }
154
158
  const nextStrategies = [];
155
159
  const npmPacksToUpdate = new Set();
156
- for (const n of names) {
157
- const current = this.readStrategy(n);
160
+ for (const strategyName of names) {
161
+ const current = this.readStrategy(strategyName);
158
162
  const src = current.source;
159
163
  if (!src) {
160
- throw new Error(`Strategy ${n} has no source. Re-add it with a URL/path source to enable update.`);
164
+ throw new Error(`Strategy ${strategyName} has no source. Re-add it with a URL/path source to enable update.`);
161
165
  }
162
166
  if (src.type === "npm") {
163
167
  npmPacksToUpdate.add(src.value);
164
168
  continue;
165
169
  }
166
170
  const fetched = await fetchStrategyFile(src.value);
167
- if (fetched.strategy.name !== n) {
168
- throw new Error(`Strategy name mismatch while updating ${n}: got ${fetched.strategy.name}`);
171
+ if (fetched.strategy.name !== strategyName) {
172
+ throw new Error(`Strategy name mismatch while updating ${strategyName}: got ${fetched.strategy.name}`);
169
173
  }
170
174
  const file = { ...fetched.strategy, source: fetched.source };
171
175
  const units = normalizeStrategyUnits(file);
172
- nextStrategies.push({ name: n, file, units });
176
+ nextStrategies.push({ name: strategyName, file, units });
173
177
  }
174
178
  if (npmPacksToUpdate.size > 0) {
175
179
  const specs = [...npmPacksToUpdate];
@@ -180,18 +184,18 @@ export class StrategyService {
180
184
  const installedPackVersions = this.packStore.readInstalledDependencyVersions();
181
185
  const updatedSpecs = specs.map((spec) => {
182
186
  const pinned = parsePinnedSpec(spec);
183
- const v = installedPackVersions[pinned.name];
184
- if (!v) {
187
+ const installedVersion = installedPackVersions[pinned.name];
188
+ if (!installedVersion) {
185
189
  throw new Error(`Strategy pack not installed after update: ${pinned.name}`);
186
190
  }
187
- return `${pinned.name}@${v}`;
191
+ return `${pinned.name}@${installedVersion}`;
188
192
  });
189
193
  const updatedFiles = [];
190
194
  updatedSpecs.forEach((spec) => {
191
195
  const pinned = parsePinnedSpec(spec);
192
196
  const root = this.packStore.resolvePackRoot(pinned.name);
193
- const files = readStrategiesFromPackRoot(root).map((f) => ({
194
- ...f,
197
+ const files = readStrategiesFromPackRoot(root).map((strategyFile) => ({
198
+ ...strategyFile,
195
199
  source: { type: "npm", value: spec }
196
200
  }));
197
201
  updatedFiles.push(...files);
@@ -202,11 +206,11 @@ export class StrategyService {
202
206
  });
203
207
  const nextConfig = this.configService.readConfigOrDefault();
204
208
  const existing = nextConfig.strategyPacks ?? [];
205
- const filtered = existing.filter((x) => {
206
- if (typeof x === "string") {
207
- return x.trim().length > 0;
209
+ const filtered = existing.filter((packEntry) => {
210
+ if (typeof packEntry === "string") {
211
+ return packEntry.trim().length > 0;
208
212
  }
209
- return !updatedSpecs.some((spec) => parsePinnedSpec(spec).name === x.name);
213
+ return !updatedSpecs.some((spec) => parsePinnedSpec(spec).name === packEntry.name);
210
214
  });
211
215
  const packsNext = [
212
216
  ...filtered,
@@ -217,26 +221,26 @@ export class StrategyService {
217
221
  ];
218
222
  this.configService.writeConfig({ ...nextConfig, strategyPacks: packsNext });
219
223
  }
220
- const allOtherNames = listStrategyNamesFromConfig(config).filter((n) => !names.includes(n));
221
- const otherStrategies = allOtherNames.map((n) => ({
222
- name: n,
223
- units: normalizeStrategyUnits(this.readStrategy(n))
224
+ const allOtherNames = listStrategyNamesFromConfig(config).filter((strategyName) => !names.includes(strategyName));
225
+ const otherStrategies = allOtherNames.map((strategyName) => ({
226
+ name: strategyName,
227
+ units: normalizeStrategyUnits(this.readStrategy(strategyName))
224
228
  }));
225
229
  const wanted = collectWantedVersions([
226
230
  ...otherStrategies,
227
- ...nextStrategies.map((s) => ({ name: s.name, units: s.units }))
231
+ ...nextStrategies.map((strategy) => ({ name: strategy.name, units: strategy.units }))
228
232
  ]);
229
233
  const conflicts = computeConflicts(wanted);
230
234
  if (conflicts.length > 0) {
231
235
  throw new Error(`Strategy version conflicts detected (${conflicts.length}). Run 'pp strategy doctor' for details.`);
232
236
  }
233
- const refs = uniq(nextStrategies.flatMap((s) => s.units.map((u) => u.ref)));
237
+ const refs = uniq(nextStrategies.flatMap((strategy) => strategy.units.map((unit) => unit.ref)));
234
238
  ensureNoInstalledVersionConflicts(installed, refs);
235
239
  refs.forEach((ref) => {
236
240
  this.unitStore.installPinned(ref);
237
241
  });
238
- nextStrategies.forEach((s) => {
239
- writeJsonAtomic(this.getStrategyPath(s.name), s.file);
242
+ nextStrategies.forEach((strategy) => {
243
+ writeJsonAtomic(this.getStrategyPath(strategy.name), strategy.file);
240
244
  });
241
245
  }
242
246
  pinStrategy(name) {
@@ -246,23 +250,28 @@ export class StrategyService {
246
250
  const file = this.readStrategy(name);
247
251
  const units = normalizeStrategyUnits(file);
248
252
  const installed = this.unitStore.readInstalledDependencyVersions();
249
- const pinnedUnits = units.map((u) => {
250
- const pinned = parsePinnedSpec(u.ref);
251
- const v = installed[pinned.name];
252
- if (!v) {
253
+ const pinnedUnits = units.map((unit) => {
254
+ const pinned = parsePinnedSpec(unit.ref);
255
+ const installedVersion = installed[pinned.name];
256
+ if (!installedVersion) {
253
257
  throw new Error(`Unit package is not installed: ${pinned.name}`);
254
258
  }
255
- const nextRef = `${pinned.name}@${v}`;
256
- return u.args.length === 0 ? nextRef : { ref: nextRef, args: u.args };
259
+ const nextRef = `${pinned.name}@${installedVersion}`;
260
+ return unit.args.length === 0 ? nextRef : { ref: nextRef, args: unit.args };
257
261
  });
258
262
  const others = listStrategyNamesFromConfig(config)
259
- .filter((n) => n !== name)
260
- .map((n) => ({ name: n, units: normalizeStrategyUnits(this.readStrategy(n)) }));
263
+ .filter((strategyName) => strategyName !== name)
264
+ .map((strategyName) => ({
265
+ name: strategyName,
266
+ units: normalizeStrategyUnits(this.readStrategy(strategyName))
267
+ }));
261
268
  const nextWanted = collectWantedVersions([
262
269
  ...others,
263
270
  {
264
271
  name,
265
- units: pinnedUnits.map((x) => typeof x === "string" ? { ref: x, args: [] } : { ref: x.ref, args: x.args ?? [] })
272
+ units: pinnedUnits.map((pinnedUnit) => typeof pinnedUnit === "string"
273
+ ? { ref: pinnedUnit, args: [] }
274
+ : { ref: pinnedUnit.ref, args: pinnedUnit.args ?? [] })
266
275
  }
267
276
  ]);
268
277
  const conflicts = computeConflicts(nextWanted);
@@ -283,15 +292,15 @@ export class StrategyService {
283
292
  const config = this.configService.readConfigOrDefault();
284
293
  const names = listStrategyNamesFromConfig(config);
285
294
  const installed = this.unitStore.readInstalledDependencyVersions();
286
- const strategies = names.map((n) => ({
287
- name: n,
288
- units: normalizeStrategyUnits(this.readStrategy(n))
295
+ const strategies = names.map((strategyName) => ({
296
+ name: strategyName,
297
+ units: normalizeStrategyUnits(this.readStrategy(strategyName))
289
298
  }));
290
299
  const wanted = collectWantedVersions(strategies);
291
300
  const conflicts = computeConflicts(wanted);
292
301
  const drift = strategies
293
- .map((s) => computeDrift({ strategyName: s.name, units: s.units, installed }))
294
- .filter((d) => d.items.length > 0);
302
+ .map((strategy) => computeDrift({ strategyName: strategy.name, units: strategy.units, installed }))
303
+ .filter((drift) => drift.items.length > 0);
295
304
  return { conflicts, drift };
296
305
  }
297
306
  }
@@ -1,5 +1,5 @@
1
- import { installPinnedPackage, updatePackageToLatest } from "../user-packages/user-package-installer.js";
2
1
  import { parsePinnedSpec } from "../user-packages/parse-pinned-spec.js";
2
+ import { installPinnedPackage, updatePackageToLatest } from "../user-packages/user-package-installer.js";
3
3
  import { UserPackageStore } from "../user-packages/user-package-store.js";
4
4
  import { isUnitLike, resolveUnitConstructor } from "./unit-validate.js";
5
5
  const UNITS_KIND = "units";
@@ -1,9 +1,5 @@
1
- const isRecord = (value) => {
2
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3
- };
4
- const isCallable = (value) => {
5
- return typeof value === "function";
6
- };
1
+ const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
2
+ const isCallable = (value) => typeof value === "function";
7
3
  export const isUnitLike = (value) => {
8
4
  if (!isRecord(value)) {
9
5
  return false;
@@ -35,7 +31,8 @@ export const resolveUnitConstructor = (mod) => {
35
31
  if (typeof mod.default === "function") {
36
32
  return mod.default;
37
33
  }
38
- const fallback = Object.values(mod).find((v) => typeof v === "function" && v.name?.endsWith("Unit"));
34
+ const fallback = Object.values(mod).find((exportedValue) => typeof exportedValue === "function" &&
35
+ exportedValue.name?.endsWith("Unit"));
39
36
  if (typeof fallback === "function") {
40
37
  return fallback;
41
38
  }
@@ -1,12 +1,8 @@
1
- import path from "node:path";
2
1
  import { createRequire } from "node:module";
2
+ import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- const isRecord = (value) => {
5
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
6
- };
7
- const isExactSemver = (value) => {
8
- return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(value);
9
- };
4
+ const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
5
+ const isExactSemver = (value) => /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(value);
10
6
  export const parsePinnedSpec = (input) => {
11
7
  const trimmed = input.trim();
12
8
  if (!trimmed) {
@@ -6,16 +6,16 @@ const toError = (value) => {
6
6
  return new Error(String(value));
7
7
  };
8
8
  const tryRun = (cwd, cmd, args) => {
9
- const r = spawnSync(cmd, args, {
9
+ const result = spawnSync(cmd, args, {
10
10
  cwd,
11
11
  stdio: "inherit",
12
12
  shell: false
13
13
  });
14
- if (r.error) {
15
- return { ok: false, error: toError(r.error) };
14
+ if (result.error) {
15
+ return { ok: false, error: toError(result.error) };
16
16
  }
17
- if (typeof r.status === "number" && r.status !== 0) {
18
- return { ok: false, error: new Error(`${cmd} exited with code ${r.status}`) };
17
+ if (typeof result.status === "number" && result.status !== 0) {
18
+ return { ok: false, error: new Error(`${cmd} exited with code ${result.status}`) };
19
19
  }
20
20
  return { ok: true };
21
21
  };
@@ -3,14 +3,12 @@ import { createRequire } from "node:module";
3
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { parseJson } from "../../utils/parse-json.js";
6
- const isRecord = (value) => {
7
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8
- };
6
+ const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
9
7
  const isStringRecord = (value) => {
10
8
  if (!value || typeof value !== "object" || Array.isArray(value)) {
11
9
  return false;
12
10
  }
13
- return Object.values(value).every((v) => typeof v === "string");
11
+ return Object.values(value).every((entryValue) => typeof entryValue === "string");
14
12
  };
15
13
  export class UserPackageStore {
16
14
  configService;
@@ -55,11 +53,13 @@ export class UserPackageStore {
55
53
  if (!json.ok || !isRecord(json.value)) {
56
54
  return { name: packageName };
57
55
  }
58
- const o = json.value;
56
+ const packageJsonRecord = json.value;
59
57
  return {
60
- name: typeof o.name === "string" ? o.name : packageName,
61
- description: typeof o.description === "string" ? o.description : undefined,
62
- version: typeof o.version === "string" ? o.version : undefined
58
+ name: typeof packageJsonRecord.name === "string" ? packageJsonRecord.name : packageName,
59
+ description: typeof packageJsonRecord.description === "string"
60
+ ? packageJsonRecord.description
61
+ : undefined,
62
+ version: typeof packageJsonRecord.version === "string" ? packageJsonRecord.version : undefined
63
63
  };
64
64
  }
65
65
  catch {
@@ -1,3 +1 @@
1
- export const uniq = (items) => {
2
- return [...new Set(items)];
3
- };
1
+ export const uniq = (items) => [...new Set(items)];
@@ -1,5 +1,5 @@
1
- import path from "node:path";
2
1
  import { createRequire } from "node:module";
2
+ import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  export const parsePluginSpec = (spec) => {
5
5
  const trimmed = spec.trim();
package/dist/view.js CHANGED
@@ -31,8 +31,8 @@ export const createViewServer = async (args) => {
31
31
  });
32
32
  });
33
33
  const server = await new Promise((resolve, reject) => {
34
- const s = app.listen(args.port, args.host, () => resolve(s));
35
- s.on("error", reject);
34
+ const httpServer = app.listen(args.port, args.host, () => resolve(httpServer));
35
+ httpServer.on("error", reject);
36
36
  });
37
37
  const address = server.address();
38
38
  const port = typeof address === "object" && address ? address.port : args.port;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagepocket/cli",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "CLI for capturing offline snapshots of web pages.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,22 +22,25 @@
22
22
  "koa-static": "^5.0.0",
23
23
  "npm-package-arg": "^13.0.2",
24
24
  "ora": "^9.0.0",
25
- "@pagepocket/build-snapshot-unit": "0.11.1",
26
- "@pagepocket/capture-http-cdp-unit": "0.11.1",
27
- "@pagepocket/capture-http-puppeteer-unit": "0.11.1",
28
- "@pagepocket/capture-http-lighterceptor-unit": "0.11.1",
29
- "@pagepocket/builtin-strategy": "0.11.1",
30
- "@pagepocket/contracts": "0.11.1",
31
- "@pagepocket/lib": "0.11.1",
32
- "@pagepocket/plugin-yt-dlp": "0.11.1",
33
- "@pagepocket/single-file-unit": "0.11.1",
34
- "@pagepocket/write-down-unit": "0.11.1"
25
+ "@pagepocket/main-content-unit": "0.12.0",
26
+ "@pagepocket/build-snapshot-unit": "0.12.0",
27
+ "@pagepocket/capture-http-cdp-unit": "0.12.0",
28
+ "@pagepocket/capture-http-lighterceptor-unit": "0.12.0",
29
+ "@pagepocket/capture-http-puppeteer-unit": "0.12.0",
30
+ "@pagepocket/contracts": "0.12.0",
31
+ "@pagepocket/builtin-strategy": "0.12.0",
32
+ "@pagepocket/lib": "0.12.0",
33
+ "@pagepocket/plugin-yt-dlp": "0.12.0",
34
+ "@pagepocket/shared": "0.12.0",
35
+ "@pagepocket/single-file-unit": "0.12.0",
36
+ "@pagepocket/write-down-unit": "0.12.0"
35
37
  },
36
38
  "devDependencies": {
37
39
  "@types/koa": "^2.15.0",
38
40
  "@types/koa-send": "^4.1.5",
39
41
  "@types/koa-static": "^4.0.4",
40
42
  "@types/node": "^20.11.30",
43
+ "rimraf": "^6.0.1",
41
44
  "tsx": "^4.19.3",
42
45
  "typescript": "^5.4.5"
43
46
  },
@@ -50,7 +53,7 @@
50
53
  }
51
54
  },
52
55
  "scripts": {
53
- "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc",
56
+ "build": "rimraf dist && tsc",
54
57
  "start": "node dist/index.js",
55
58
  "test": "pnpm build && tsx --test specs/*.spec.ts"
56
59
  }