@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.
- package/dist/commands/archive.js +18 -18
- package/dist/commands/plugin/set.js +1 -3
- package/dist/commands/strategy/doctor.js +6 -6
- package/dist/commands/strategy/ls.js +25 -8
- 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 +5 -7
- package/dist/services/strategy/strategy-pack-read.js +2 -4
- package/dist/services/strategy/strategy-pack-store.js +1 -1
- 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 -7
- 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/utils/array.js +1 -3
- package/dist/utils/parse-plugin-spec.js +1 -1
- package/dist/view.js +2 -2
- package/package.json +15 -12
package/dist/commands/archive.js
CHANGED
|
@@ -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 {
|
|
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
|
|
50
|
-
if (seen.has(
|
|
49
|
+
const duplicateUnit = input.units.find((unit) => {
|
|
50
|
+
if (seen.has(unit.id)) {
|
|
51
51
|
return true;
|
|
52
52
|
}
|
|
53
|
-
seen.add(
|
|
53
|
+
seen.add(unit.id);
|
|
54
54
|
return false;
|
|
55
55
|
});
|
|
56
|
-
if (
|
|
57
|
-
throw new Error(`Duplicate unit id detected in strategy ${input.strategyName}: ${
|
|
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((
|
|
68
|
-
const pinned = parsePinnedSpec(
|
|
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((
|
|
84
|
-
const installedText = typeof
|
|
85
|
-
return `${
|
|
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 (
|
|
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 (
|
|
116
|
-
ref:
|
|
117
|
-
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((
|
|
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,39 @@
|
|
|
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 { 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
|
|
11
|
-
const
|
|
12
|
-
const names = uniq([...
|
|
13
|
-
.map((
|
|
14
|
-
.filter((
|
|
15
|
-
.sort((
|
|
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
|
-
|
|
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
|
|
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);
|
|
@@ -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
|
|
10
|
-
if (
|
|
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((
|
|
12
|
+
return value.every((item) => isJsonValue(item));
|
|
15
13
|
}
|
|
16
14
|
if (isRecord(value)) {
|
|
17
|
-
return Object.values(value).every((
|
|
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((
|
|
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 {
|
|
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((
|
|
59
|
-
const refs = uniq(allUnits.map((
|
|
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((
|
|
76
|
+
const nextConfig = installedStrategies.reduce((configAccumulator, strategyName) => withStrategyInConfig(configAccumulator, strategyName), config);
|
|
77
77
|
const existing = nextConfig.strategyPacks ?? [];
|
|
78
|
-
const filtered = existing.filter((
|
|
79
|
-
if (typeof
|
|
80
|
-
return
|
|
78
|
+
const filtered = existing.filter((packEntry) => {
|
|
79
|
+
if (typeof packEntry === "string") {
|
|
80
|
+
return packEntry.trim().length > 0;
|
|
81
81
|
}
|
|
82
|
-
return
|
|
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((
|
|
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((
|
|
132
|
-
.filter((
|
|
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((
|
|
138
|
-
name:
|
|
139
|
-
units: normalizeStrategyUnits(this.readStrategy(
|
|
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((
|
|
148
|
-
|
|
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
|
|
157
|
-
const current = this.readStrategy(
|
|
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 ${
|
|
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 !==
|
|
168
|
-
throw new Error(`Strategy name mismatch while updating ${
|
|
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:
|
|
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
|
|
184
|
-
if (!
|
|
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}@${
|
|
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((
|
|
194
|
-
...
|
|
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((
|
|
206
|
-
if (typeof
|
|
207
|
-
return
|
|
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 ===
|
|
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((
|
|
221
|
-
const otherStrategies = allOtherNames.map((
|
|
222
|
-
name:
|
|
223
|
-
units: normalizeStrategyUnits(this.readStrategy(
|
|
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((
|
|
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((
|
|
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((
|
|
239
|
-
writeJsonAtomic(this.getStrategyPath(
|
|
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((
|
|
250
|
-
const pinned = parsePinnedSpec(
|
|
251
|
-
const
|
|
252
|
-
if (!
|
|
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}@${
|
|
256
|
-
return
|
|
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((
|
|
260
|
-
.map((
|
|
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((
|
|
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((
|
|
287
|
-
name:
|
|
288
|
-
units: normalizeStrategyUnits(this.readStrategy(
|
|
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((
|
|
294
|
-
.filter((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
9
|
+
const result = spawnSync(cmd, args, {
|
|
10
10
|
cwd,
|
|
11
11
|
stdio: "inherit",
|
|
12
12
|
shell: false
|
|
13
13
|
});
|
|
14
|
-
if (
|
|
15
|
-
return { ok: false, error: toError(
|
|
14
|
+
if (result.error) {
|
|
15
|
+
return { ok: false, error: toError(result.error) };
|
|
16
16
|
}
|
|
17
|
-
if (typeof
|
|
18
|
-
return { ok: false, error: new Error(`${cmd} exited with code ${
|
|
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((
|
|
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
|
|
56
|
+
const packageJsonRecord = json.value;
|
|
59
57
|
return {
|
|
60
|
-
name: typeof
|
|
61
|
-
description: typeof
|
|
62
|
-
|
|
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 {
|
package/dist/utils/array.js
CHANGED
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
|
|
35
|
-
|
|
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.
|
|
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/
|
|
26
|
-
"@pagepocket/
|
|
27
|
-
"@pagepocket/capture-http-
|
|
28
|
-
"@pagepocket/capture-http-lighterceptor-unit": "0.
|
|
29
|
-
"@pagepocket/
|
|
30
|
-
"@pagepocket/contracts": "0.
|
|
31
|
-
"@pagepocket/
|
|
32
|
-
"@pagepocket/
|
|
33
|
-
"@pagepocket/
|
|
34
|
-
"@pagepocket/
|
|
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": "
|
|
56
|
+
"build": "rimraf dist && tsc",
|
|
54
57
|
"start": "node dist/index.js",
|
|
55
58
|
"test": "pnpm build && tsx --test specs/*.spec.ts"
|
|
56
59
|
}
|