@pagepocket/cli 0.11.1 → 0.13.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.
- package/dist/commands/archive.js +46 -161
- package/dist/commands/plugin/set.js +1 -3
- package/dist/commands/strategy/doctor.js +6 -6
- package/dist/commands/strategy/ls.js +32 -8
- package/dist/commands/view.js +1 -1
- package/dist/services/config-service.js +8 -8
- package/dist/services/plugin-installer.js +10 -10
- package/dist/services/plugin-store.js +11 -13
- package/dist/services/strategy/builtin-strategy-registry.js +12 -21
- package/dist/services/strategy/strategy-analyze.js +6 -6
- package/dist/services/strategy/strategy-config.js +4 -4
- package/dist/services/strategy/strategy-fetch.js +2 -4
- package/dist/services/strategy/strategy-io.js +3 -9
- package/dist/services/strategy/strategy-normalize.js +43 -7
- package/dist/services/strategy/strategy-pack-read.js +2 -4
- package/dist/services/strategy/strategy-pack-store.js +1 -2
- package/dist/services/strategy/strategy-service.js +63 -54
- package/dist/services/units/unit-store.js +1 -1
- package/dist/services/units/unit-validate.js +4 -10
- package/dist/services/user-packages/parse-pinned-spec.js +3 -7
- package/dist/services/user-packages/user-package-installer.js +5 -5
- package/dist/services/user-packages/user-package-store.js +8 -8
- package/dist/units/network-observer-unit.js +1 -1
- package/dist/utils/archive-strategy.js +138 -0
- package/dist/utils/array.js +1 -3
- package/dist/utils/parse-plugin-spec.js +1 -1
- package/dist/vendor/content-reader.css +1 -0
- package/dist/vendor/content-reader.iife.js +60 -0
- package/dist/view-main-content.js +40 -0
- package/dist/view.js +71 -33
- package/package.json +21 -19
package/dist/commands/archive.js
CHANGED
|
@@ -1,139 +1,11 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import { pathToFileURL } from "node:url";
|
|
3
1
|
import { Args, Command, Flags } from "@oclif/core";
|
|
4
2
|
import { ga, ns, PagePocket } from "@pagepocket/lib";
|
|
5
3
|
import chalk from "chalk";
|
|
6
|
-
import
|
|
4
|
+
import ora from "ora";
|
|
7
5
|
import { ConfigService } from "../services/config-service.js";
|
|
8
|
-
import {
|
|
6
|
+
import { loadConfiguredPlugins } from "../services/load-configured-plugins.js";
|
|
9
7
|
import { StrategyService } from "../services/strategy/strategy-service.js";
|
|
10
|
-
import {
|
|
11
|
-
import { parsePinnedSpec } from "../services/user-packages/parse-pinned-spec.js";
|
|
12
|
-
import { isUnitLike, resolveUnitConstructor } from "../services/units/unit-validate.js";
|
|
13
|
-
import { UnitStore } from "../services/units/unit-store.js";
|
|
14
|
-
import { uniq } from "../utils/array.js";
|
|
15
|
-
import { withSpinner } from "../utils/with-spinner.js";
|
|
16
|
-
const buildMissingStrategyError = (input) => {
|
|
17
|
-
const available = uniq([...input.installedNames, ...listBuiltinStrategyNames()])
|
|
18
|
-
.filter((name) => name.trim().length > 0)
|
|
19
|
-
.sort((left, right) => left.localeCompare(right));
|
|
20
|
-
const suffix = available.length > 0
|
|
21
|
-
? ` Available strategies: ${available.join(", ")}`
|
|
22
|
-
: " No strategies found.";
|
|
23
|
-
return new Error(`Strategy not found: ${input.strategyName}.${suffix}`);
|
|
24
|
-
};
|
|
25
|
-
const resolveStrategy = (input) => {
|
|
26
|
-
const installedNames = input.strategyService.listInstalledStrategyNames();
|
|
27
|
-
if (installedNames.includes(input.strategyName)) {
|
|
28
|
-
return {
|
|
29
|
-
name: input.strategyName,
|
|
30
|
-
strategyFile: input.strategyService.readStrategy(input.strategyName),
|
|
31
|
-
source: "installed"
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
const builtin = readBuiltinStrategy(input.strategyName);
|
|
35
|
-
if (builtin) {
|
|
36
|
-
return {
|
|
37
|
-
name: input.strategyName,
|
|
38
|
-
strategyFile: builtin,
|
|
39
|
-
source: "builtin"
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
throw buildMissingStrategyError({
|
|
43
|
-
strategyName: input.strategyName,
|
|
44
|
-
installedNames
|
|
45
|
-
});
|
|
46
|
-
};
|
|
47
|
-
const assertNoDuplicateUnitIds = (input) => {
|
|
48
|
-
const seen = new Set();
|
|
49
|
-
const dup = input.units.find((u) => {
|
|
50
|
-
if (seen.has(u.id)) {
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
seen.add(u.id);
|
|
54
|
-
return false;
|
|
55
|
-
});
|
|
56
|
-
if (dup) {
|
|
57
|
-
throw new Error(`Duplicate unit id detected in strategy ${input.strategyName}: ${dup.id}`);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
const loadInstalledStrategyUnits = async (input) => {
|
|
61
|
-
const strategyService = new StrategyService(input.configService);
|
|
62
|
-
const unitStore = new UnitStore(input.configService);
|
|
63
|
-
strategyService.ensureConfigFileExists();
|
|
64
|
-
const strategyFile = strategyService.readStrategy(input.strategyName);
|
|
65
|
-
const normalized = normalizeStrategyUnits(strategyFile);
|
|
66
|
-
const installed = unitStore.readInstalledDependencyVersions();
|
|
67
|
-
const drift = normalized.flatMap((u) => {
|
|
68
|
-
const pinned = parsePinnedSpec(u.ref);
|
|
69
|
-
const installedVersion = installed[pinned.name];
|
|
70
|
-
if (!installedVersion || installedVersion !== pinned.version) {
|
|
71
|
-
return [
|
|
72
|
-
{
|
|
73
|
-
name: pinned.name,
|
|
74
|
-
pinned: pinned.version,
|
|
75
|
-
installed: installedVersion
|
|
76
|
-
}
|
|
77
|
-
];
|
|
78
|
-
}
|
|
79
|
-
return [];
|
|
80
|
-
});
|
|
81
|
-
if (drift.length > 0) {
|
|
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}`;
|
|
86
|
-
})
|
|
87
|
-
.join("; ");
|
|
88
|
-
throw new Error(`Strategy drift detected (${input.strategyName}). ${details}. ` +
|
|
89
|
-
`Fix: pp strategy update ${input.strategyName} OR pp strategy pin ${input.strategyName}`);
|
|
90
|
-
}
|
|
91
|
-
const units = await Promise.all(normalized.map(async (u) => unitStore.instantiateFromRef(u.ref, u.args)));
|
|
92
|
-
assertNoDuplicateUnitIds({
|
|
93
|
-
strategyName: input.strategyName,
|
|
94
|
-
units
|
|
95
|
-
});
|
|
96
|
-
return units;
|
|
97
|
-
};
|
|
98
|
-
const instantiateBuiltinUnit = async (input) => {
|
|
99
|
-
const req = createRequire(import.meta.url);
|
|
100
|
-
const pinned = parsePinnedSpec(input.ref);
|
|
101
|
-
const resolved = req.resolve(pinned.name);
|
|
102
|
-
const mod = (await import(pathToFileURL(resolved).href));
|
|
103
|
-
const ctor = resolveUnitConstructor(mod);
|
|
104
|
-
if (!ctor) {
|
|
105
|
-
throw new Error(`Unit ${pinned.name} does not export a Unit constructor.`);
|
|
106
|
-
}
|
|
107
|
-
const instance = new ctor(...input.args);
|
|
108
|
-
if (!isUnitLike(instance)) {
|
|
109
|
-
throw new Error(`Unit ${pinned.name} exported constructor did not produce a Unit.`);
|
|
110
|
-
}
|
|
111
|
-
return instance;
|
|
112
|
-
};
|
|
113
|
-
const loadBuiltinStrategyUnits = async (input) => {
|
|
114
|
-
const normalized = normalizeStrategyUnits(input.strategyFile);
|
|
115
|
-
const units = await Promise.all(normalized.map(async (u) => instantiateBuiltinUnit({
|
|
116
|
-
ref: u.ref,
|
|
117
|
-
args: u.args
|
|
118
|
-
})));
|
|
119
|
-
assertNoDuplicateUnitIds({
|
|
120
|
-
strategyName: input.strategyName,
|
|
121
|
-
units
|
|
122
|
-
});
|
|
123
|
-
return units;
|
|
124
|
-
};
|
|
125
|
-
const loadStrategyUnits = async (input) => {
|
|
126
|
-
if (input.strategy.source === "installed") {
|
|
127
|
-
return loadInstalledStrategyUnits({
|
|
128
|
-
strategyName: input.strategy.name,
|
|
129
|
-
configService: input.configService
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
return loadBuiltinStrategyUnits({
|
|
133
|
-
strategyName: input.strategy.name,
|
|
134
|
-
strategyFile: input.strategy.strategyFile
|
|
135
|
-
});
|
|
136
|
-
};
|
|
8
|
+
import { loadStrategyUnits, resolveStrategy } from "../utils/archive-strategy.js";
|
|
137
9
|
export default class ArchiveCommand extends Command {
|
|
138
10
|
static description = "Archive a web page as an offline snapshot.";
|
|
139
11
|
static args = {
|
|
@@ -165,38 +37,51 @@ export default class ArchiveCommand extends Command {
|
|
|
165
37
|
const timeoutMs = typeof flags.timeout === "number" ? flags.timeout : undefined;
|
|
166
38
|
const maxDurationMs = typeof flags.maxDuration === "number" ? flags.maxDuration : undefined;
|
|
167
39
|
const strategyName = typeof flags.strategy === "string" ? flags.strategy.trim() : undefined;
|
|
40
|
+
const configService = new ConfigService();
|
|
41
|
+
const name = strategyName && strategyName.length > 0 ? strategyName : "default";
|
|
42
|
+
const strategyService = new StrategyService(configService);
|
|
43
|
+
strategyService.ensureConfigFileExists();
|
|
44
|
+
const strategy = resolveStrategy({ strategyName: name, strategyService });
|
|
45
|
+
const units = await loadStrategyUnits({ strategy, configService });
|
|
46
|
+
const strategyFile = strategy.strategyFile;
|
|
47
|
+
const captureOptions = strategyFile.pipeline.captureOptions;
|
|
48
|
+
const effectiveTimeoutMs = typeof timeoutMs === "number" ? timeoutMs : captureOptions?.timeoutMs;
|
|
49
|
+
const effectiveMaxDurationMs = typeof maxDurationMs === "number" ? maxDurationMs : captureOptions?.maxDurationMs;
|
|
168
50
|
const pagepocket = PagePocket.fromURL(targetUrl);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
configService
|
|
182
|
-
});
|
|
183
|
-
const entryTarget = pagepocket;
|
|
184
|
-
const strategyFile = strategy.strategyFile;
|
|
185
|
-
const captureOptions = strategyFile.pipeline.captureOptions;
|
|
186
|
-
const effectiveTimeoutMs = typeof timeoutMs === "number" ? timeoutMs : captureOptions?.timeoutMs;
|
|
187
|
-
const effectiveMaxDurationMs = typeof maxDurationMs === "number" ? maxDurationMs : captureOptions?.maxDurationMs;
|
|
188
|
-
const result = await entryTarget.capture({
|
|
189
|
-
...(typeof effectiveTimeoutMs === "number" ? { timeoutMs: effectiveTimeoutMs } : {}),
|
|
190
|
-
...(typeof effectiveMaxDurationMs === "number"
|
|
191
|
-
? { maxDurationMs: effectiveMaxDurationMs }
|
|
192
|
-
: {}),
|
|
193
|
-
blacklist: [...ga, ...ns],
|
|
194
|
-
units,
|
|
195
|
-
plugins: await loadConfiguredPlugins().catch(() => [])
|
|
196
|
-
});
|
|
197
|
-
return result;
|
|
51
|
+
let spinner = ora();
|
|
52
|
+
let activeUnitId = "";
|
|
53
|
+
let activeUnitLabel = "";
|
|
54
|
+
const formatUnitLabel = (index, total, unitDescription) => `[${index + 1}/${total}] ${unitDescription}`;
|
|
55
|
+
pagepocket.on("unit:start", (e) => {
|
|
56
|
+
activeUnitId = e.unitId;
|
|
57
|
+
activeUnitLabel = formatUnitLabel(e.index, e.total, e.unitDescription);
|
|
58
|
+
spinner = ora(activeUnitLabel).start();
|
|
59
|
+
});
|
|
60
|
+
pagepocket.on("unit:log", (e) => {
|
|
61
|
+
if (e.unitId !== activeUnitId) {
|
|
62
|
+
return;
|
|
198
63
|
}
|
|
199
|
-
|
|
64
|
+
spinner.text = `${activeUnitLabel} ${chalk.gray(e.message)}`;
|
|
65
|
+
});
|
|
66
|
+
pagepocket.on("unit:end", (e) => {
|
|
67
|
+
spinner.succeed(formatUnitLabel(e.index, e.total, e.unitDescription));
|
|
68
|
+
});
|
|
69
|
+
let result;
|
|
70
|
+
try {
|
|
71
|
+
result = await pagepocket.capture({
|
|
72
|
+
...(typeof effectiveTimeoutMs === "number" ? { timeoutMs: effectiveTimeoutMs } : {}),
|
|
73
|
+
...(typeof effectiveMaxDurationMs === "number"
|
|
74
|
+
? { maxDurationMs: effectiveMaxDurationMs }
|
|
75
|
+
: {}),
|
|
76
|
+
blacklist: [...ga, ...ns],
|
|
77
|
+
units,
|
|
78
|
+
plugins: await loadConfiguredPlugins().catch(() => [])
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
spinner.fail();
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
200
85
|
this.log(chalk.green("All done! Snapshot created."));
|
|
201
86
|
if (result.kind === "zip") {
|
|
202
87
|
this.log(`Snapshot saved to ${chalk.cyan(result.zip.outputPath)}`);
|
|
@@ -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((
|
|
10
|
-
this.log(chalk.red(`[CONFLICT] ${
|
|
11
|
-
Object.entries(
|
|
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((
|
|
16
|
-
this.log(chalk.yellow(`[DRIFT] ${
|
|
17
|
-
|
|
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,46 @@
|
|
|
1
1
|
import { Command } from "@oclif/core";
|
|
2
|
-
import
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { listBuiltinStrategyNames, readBuiltinStrategy } from "../../services/strategy/builtin-strategy-registry.js";
|
|
4
|
+
import { normalizeBuiltinStrategyUnits, 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 formatInstalledStrategy = (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
|
+
};
|
|
12
|
+
const formatBuiltinStrategy = (name, strategyFile) => {
|
|
13
|
+
const units = normalizeBuiltinStrategyUnits(strategyFile);
|
|
14
|
+
const pipeline = units.map((unit) => unit.name).join(" -> ");
|
|
15
|
+
return pipeline.length > 0 ? `${name}${chalk.dim(`(${pipeline})`)}` : name;
|
|
16
|
+
};
|
|
5
17
|
export default class StrategyLsCommand extends Command {
|
|
6
18
|
static description = "List available strategies.";
|
|
7
19
|
async run() {
|
|
8
20
|
const service = new StrategyService();
|
|
9
21
|
service.ensureConfigFileExists();
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const names = uniq([...
|
|
13
|
-
.map((
|
|
14
|
-
.filter((
|
|
15
|
-
.sort((
|
|
22
|
+
const builtinNames = listBuiltinStrategyNames();
|
|
23
|
+
const installedNames = service.listInstalledStrategyNames();
|
|
24
|
+
const names = uniq([...builtinNames, ...installedNames])
|
|
25
|
+
.map((name) => name.trim())
|
|
26
|
+
.filter((name) => name.length > 0)
|
|
27
|
+
.sort((left, right) => left.localeCompare(right));
|
|
16
28
|
if (names.length === 0) {
|
|
17
29
|
this.log("No strategies available.");
|
|
18
30
|
return;
|
|
19
31
|
}
|
|
20
|
-
|
|
32
|
+
for (const name of names) {
|
|
33
|
+
const isInstalled = installedNames.includes(name);
|
|
34
|
+
const strategyFile = isInstalled ? service.readStrategy(name) : readBuiltinStrategy(name);
|
|
35
|
+
if (strategyFile) {
|
|
36
|
+
const formatted = isInstalled
|
|
37
|
+
? formatInstalledStrategy(name, strategyFile)
|
|
38
|
+
: formatBuiltinStrategy(name, strategyFile);
|
|
39
|
+
this.log(formatted);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
this.log(name);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
21
45
|
}
|
|
22
46
|
}
|
package/dist/commands/view.js
CHANGED
|
@@ -51,7 +51,7 @@ export default class ViewCommand extends Command {
|
|
|
51
51
|
host: flags.host,
|
|
52
52
|
port
|
|
53
53
|
});
|
|
54
|
-
this.log(`Serving ${chalk.cyan(rootDir)}`);
|
|
54
|
+
this.log(`Serving ${chalk.cyan(rootDir)} ${chalk.dim(`(${server.snapshotType})`)}`);
|
|
55
55
|
this.log(chalk.green(server.url));
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -15,8 +15,8 @@ export class ConfigService {
|
|
|
15
15
|
}
|
|
16
16
|
getConfigDir() {
|
|
17
17
|
if (process.platform === "win32") {
|
|
18
|
-
const
|
|
19
|
-
return
|
|
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
|
|
28
|
-
return
|
|
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((
|
|
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((
|
|
82
|
-
.filter((
|
|
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((
|
|
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
|
|
10
|
+
const result = spawnSync(cmd, args, {
|
|
11
11
|
cwd: installDir,
|
|
12
12
|
stdio: "inherit",
|
|
13
13
|
shell: false
|
|
14
14
|
});
|
|
15
|
-
if (
|
|
16
|
-
return { ok: false, error:
|
|
15
|
+
if (result.error) {
|
|
16
|
+
return { ok: false, error: result.error };
|
|
17
17
|
}
|
|
18
|
-
if (typeof
|
|
19
|
-
return { ok: false, error: new Error(`${cmd} exited with code ${
|
|
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
|
|
54
|
+
const result = spawnSync(cmd, args, {
|
|
55
55
|
cwd: installDir,
|
|
56
56
|
stdio: "inherit",
|
|
57
57
|
shell: false
|
|
58
58
|
});
|
|
59
|
-
if (
|
|
60
|
-
return { ok: false, error:
|
|
59
|
+
if (result.error) {
|
|
60
|
+
return { ok: false, error: result.error };
|
|
61
61
|
}
|
|
62
|
-
if (typeof
|
|
63
|
-
return { ok: false, error: new Error(`${cmd} exited with code ${
|
|
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
|
-
|
|
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((
|
|
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
|
|
79
|
+
const packageJsonRecord = json.value;
|
|
84
80
|
return {
|
|
85
|
-
name: typeof
|
|
86
|
-
description: typeof
|
|
87
|
-
|
|
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((
|
|
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((
|
|
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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
5
|
-
for (const
|
|
6
|
-
const pinned = parsePinnedSpec(
|
|
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(
|
|
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((
|
|
27
|
-
const pinned = parsePinnedSpec(
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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);
|