@pagepocket/cli 0.10.1 → 0.11.1
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 +146 -76
- package/dist/commands/plugin/add.js +19 -25
- package/dist/commands/plugin/doctor.js +22 -28
- package/dist/commands/plugin/ls.js +16 -22
- package/dist/commands/plugin/prune.js +13 -19
- package/dist/commands/plugin/remove.js +16 -22
- package/dist/commands/plugin/set.js +13 -19
- package/dist/commands/plugin/uninstall.js +29 -35
- package/dist/commands/plugin/update.js +22 -28
- package/dist/commands/strategy/add.js +14 -20
- package/dist/commands/strategy/doctor.js +11 -17
- package/dist/commands/strategy/ls.js +22 -0
- package/dist/commands/strategy/pin.js +10 -16
- package/dist/commands/strategy/remove.js +10 -16
- package/dist/commands/strategy/update.js +21 -27
- package/dist/commands/view.js +36 -42
- package/dist/index.js +9 -7
- package/dist/lib/filename.js +1 -5
- package/dist/services/config-service.js +24 -30
- package/dist/services/load-configured-plugins.js +8 -12
- package/dist/services/plugin-installer.js +6 -12
- package/dist/services/plugin-store.js +27 -68
- package/dist/services/strategy/builtin-strategy-registry.js +23 -0
- package/dist/services/strategy/strategy-analyze.js +10 -20
- package/dist/services/strategy/strategy-config.js +6 -13
- package/dist/services/strategy/strategy-fetch.js +11 -18
- package/dist/services/strategy/strategy-io.js +15 -25
- package/dist/services/strategy/strategy-normalize.js +4 -8
- package/dist/services/strategy/strategy-pack-read.js +10 -17
- package/dist/services/strategy/strategy-pack-store.js +12 -18
- package/dist/services/strategy/strategy-service.js +79 -82
- package/dist/services/strategy/types.js +1 -2
- package/dist/services/units/unit-store.js +16 -20
- package/dist/services/units/unit-validate.js +20 -5
- package/dist/services/user-packages/parse-pinned-spec.js +6 -6
- package/dist/services/user-packages/user-package-installer.js +4 -9
- package/dist/services/user-packages/user-package-store.js +19 -57
- package/dist/stages/prepare-output.js +6 -13
- package/dist/units/network-observer-unit.js +7 -10
- package/dist/utils/array.js +3 -0
- package/dist/utils/normalize-argv.js +3 -8
- package/dist/utils/parse-json.js +1 -5
- package/dist/utils/parse-plugin-options.js +1 -5
- package/dist/utils/parse-plugin-spec.js +6 -6
- package/dist/utils/validate-plugin-default-export.js +1 -5
- package/dist/utils/with-spinner.js +3 -10
- package/dist/view.js +12 -19
- package/package.json +12 -11
- package/dist/services/strategy/read-installed-package-version.js +0 -28
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizeStrategyUnits = void 0;
|
|
4
|
-
const parse_pinned_spec_1 = require("../user-packages/parse-pinned-spec");
|
|
1
|
+
import { parsePinnedSpec } from "../user-packages/parse-pinned-spec.js";
|
|
5
2
|
const isRecord = (value) => {
|
|
6
3
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7
4
|
};
|
|
@@ -23,7 +20,7 @@ const isJsonValue = (value) => {
|
|
|
23
20
|
};
|
|
24
21
|
const normalizeUnitSpec = (spec, idx) => {
|
|
25
22
|
if (typeof spec === "string") {
|
|
26
|
-
const pinned =
|
|
23
|
+
const pinned = parsePinnedSpec(spec);
|
|
27
24
|
return { ref: pinned.spec, args: [] };
|
|
28
25
|
}
|
|
29
26
|
if (!isRecord(spec)) {
|
|
@@ -33,7 +30,7 @@ const normalizeUnitSpec = (spec, idx) => {
|
|
|
33
30
|
if (typeof ref !== "string") {
|
|
34
31
|
throw new Error(`Invalid unit ref at index ${idx}`);
|
|
35
32
|
}
|
|
36
|
-
const pinned =
|
|
33
|
+
const pinned = parsePinnedSpec(ref);
|
|
37
34
|
const argsRaw = spec.args;
|
|
38
35
|
const args = typeof argsRaw === "undefined" ? [] : argsRaw;
|
|
39
36
|
if (!Array.isArray(args)) {
|
|
@@ -44,8 +41,7 @@ const normalizeUnitSpec = (spec, idx) => {
|
|
|
44
41
|
}
|
|
45
42
|
return { ref: pinned.spec, args };
|
|
46
43
|
};
|
|
47
|
-
const normalizeStrategyUnits = (strategy) => {
|
|
44
|
+
export const normalizeStrategyUnits = (strategy) => {
|
|
48
45
|
const units = strategy.pipeline.units;
|
|
49
46
|
return units.map((spec, idx) => normalizeUnitSpec(spec, idx));
|
|
50
47
|
};
|
|
51
|
-
exports.normalizeStrategyUnits = normalizeStrategyUnits;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.readStrategiesFromPackRoot = void 0;
|
|
7
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const parse_json_1 = require("../../utils/parse-json");
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parseJson } from "../../utils/parse-json.js";
|
|
10
4
|
const isRecord = (value) => {
|
|
11
5
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
12
6
|
};
|
|
@@ -29,19 +23,19 @@ const isStrategyFile = (value) => {
|
|
|
29
23
|
}
|
|
30
24
|
return true;
|
|
31
25
|
};
|
|
32
|
-
const readStrategiesFromPackRoot = (packRoot) => {
|
|
33
|
-
const strategiesDir =
|
|
34
|
-
if (!
|
|
26
|
+
export const readStrategiesFromPackRoot = (packRoot) => {
|
|
27
|
+
const strategiesDir = path.join(packRoot, "strategies");
|
|
28
|
+
if (!fs.existsSync(strategiesDir)) {
|
|
35
29
|
return [];
|
|
36
30
|
}
|
|
37
|
-
const fileNames =
|
|
31
|
+
const fileNames = fs
|
|
38
32
|
.readdirSync(strategiesDir)
|
|
39
33
|
.filter((f) => f.endsWith(".strategy.json"))
|
|
40
34
|
.sort((a, b) => a.localeCompare(b));
|
|
41
35
|
return fileNames.map((fileName) => {
|
|
42
|
-
const filePath =
|
|
43
|
-
const text =
|
|
44
|
-
const parsed =
|
|
36
|
+
const filePath = path.join(strategiesDir, fileName);
|
|
37
|
+
const text = fs.readFileSync(filePath, "utf8");
|
|
38
|
+
const parsed = parseJson(text);
|
|
45
39
|
if (!parsed.ok) {
|
|
46
40
|
throw parsed.error;
|
|
47
41
|
}
|
|
@@ -51,4 +45,3 @@ const readStrategiesFromPackRoot = (packRoot) => {
|
|
|
51
45
|
return parsed.value;
|
|
52
46
|
});
|
|
53
47
|
};
|
|
54
|
-
exports.readStrategiesFromPackRoot = readStrategiesFromPackRoot;
|
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
exports.StrategyPackStore = void 0;
|
|
7
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
-
const user_package_installer_1 = require("../user-packages/user-package-installer");
|
|
9
|
-
const user_package_installer_2 = require("../user-packages/user-package-installer");
|
|
10
|
-
const parse_pinned_spec_1 = require("../user-packages/parse-pinned-spec");
|
|
11
|
-
const user_package_store_1 = require("../user-packages/user-package-store");
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { installPinnedPackage } from "../user-packages/user-package-installer.js";
|
|
3
|
+
import { updatePackageToLatest } from "../user-packages/user-package-installer.js";
|
|
4
|
+
import { parsePinnedSpec } from "../user-packages/parse-pinned-spec.js";
|
|
5
|
+
import { UserPackageStore } from "../user-packages/user-package-store.js";
|
|
12
6
|
const STRATEGY_PACKS_KIND = "strategy-packs";
|
|
13
7
|
const STRATEGY_PACKS_PACKAGE_JSON_NAME = "pagepocket-user-strategy-packs";
|
|
14
|
-
class StrategyPackStore {
|
|
8
|
+
export class StrategyPackStore {
|
|
9
|
+
store;
|
|
15
10
|
constructor(configService) {
|
|
16
|
-
this.store = new
|
|
11
|
+
this.store = new UserPackageStore(configService, STRATEGY_PACKS_KIND);
|
|
17
12
|
}
|
|
18
13
|
getInstallDir() {
|
|
19
14
|
return this.store.getInstallDir();
|
|
@@ -22,15 +17,15 @@ class StrategyPackStore {
|
|
|
22
17
|
return this.store.readInstalledDependencyVersions(STRATEGY_PACKS_PACKAGE_JSON_NAME);
|
|
23
18
|
}
|
|
24
19
|
installPinned(spec) {
|
|
25
|
-
const pinned =
|
|
26
|
-
|
|
20
|
+
const pinned = parsePinnedSpec(spec);
|
|
21
|
+
installPinnedPackage(this.store, {
|
|
27
22
|
packageJsonName: STRATEGY_PACKS_PACKAGE_JSON_NAME,
|
|
28
23
|
packageSpec: pinned.spec
|
|
29
24
|
});
|
|
30
25
|
return pinned;
|
|
31
26
|
}
|
|
32
27
|
updateToLatest(packageName) {
|
|
33
|
-
|
|
28
|
+
updatePackageToLatest(this.store, {
|
|
34
29
|
packageJsonName: STRATEGY_PACKS_PACKAGE_JSON_NAME,
|
|
35
30
|
packageName
|
|
36
31
|
});
|
|
@@ -39,7 +34,6 @@ class StrategyPackStore {
|
|
|
39
34
|
this.store.ensureInstallDirPackageJson(STRATEGY_PACKS_PACKAGE_JSON_NAME);
|
|
40
35
|
const req = this.store.createRequire(STRATEGY_PACKS_PACKAGE_JSON_NAME);
|
|
41
36
|
const pkgJsonPath = req.resolve(`${packageName}/package.json`);
|
|
42
|
-
return
|
|
37
|
+
return path.dirname(pkgJsonPath);
|
|
43
38
|
}
|
|
44
39
|
}
|
|
45
|
-
exports.StrategyPackStore = StrategyPackStore;
|
|
@@ -1,41 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class StrategyService {
|
|
19
|
-
constructor(configService = new config_service_1.ConfigService()) {
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { ConfigService } from "../config-service.js";
|
|
3
|
+
import { parsePinnedSpec } from "../user-packages/parse-pinned-spec.js";
|
|
4
|
+
import { UnitStore } from "../units/unit-store.js";
|
|
5
|
+
import { uniq } from "../../utils/array.js";
|
|
6
|
+
import { collectWantedVersions, computeConflicts, computeDrift, ensureNoInstalledVersionConflicts } from "./strategy-analyze.js";
|
|
7
|
+
import { listStrategyNamesFromConfig, requireStrategyInstalled, withStrategyInConfig, withoutStrategyInConfig } from "./strategy-config.js";
|
|
8
|
+
import { fetchStrategyFile } from "./strategy-fetch.js";
|
|
9
|
+
import { getStrategyPath, getStrategiesDir, readStrategyFile, writeJsonAtomic } from "./strategy-io.js";
|
|
10
|
+
import { normalizeStrategyUnits } from "./strategy-normalize.js";
|
|
11
|
+
import { readStrategiesFromPackRoot } from "./strategy-pack-read.js";
|
|
12
|
+
import { StrategyPackStore } from "./strategy-pack-store.js";
|
|
13
|
+
export class StrategyService {
|
|
14
|
+
configService;
|
|
15
|
+
unitStore;
|
|
16
|
+
packStore;
|
|
17
|
+
constructor(configService = new ConfigService()) {
|
|
20
18
|
this.configService = configService;
|
|
21
|
-
this.unitStore = new
|
|
22
|
-
this.packStore = new
|
|
19
|
+
this.unitStore = new UnitStore(configService);
|
|
20
|
+
this.packStore = new StrategyPackStore(configService);
|
|
23
21
|
}
|
|
24
22
|
ensureConfigFileExists() {
|
|
25
23
|
return this.configService.ensureConfigFileExists();
|
|
26
24
|
}
|
|
27
25
|
getStrategiesDir() {
|
|
28
|
-
return
|
|
26
|
+
return getStrategiesDir(this.configService);
|
|
29
27
|
}
|
|
30
28
|
getStrategyPath(name) {
|
|
31
|
-
return
|
|
29
|
+
return getStrategyPath(this.configService, name);
|
|
32
30
|
}
|
|
33
31
|
readStrategy(name) {
|
|
34
|
-
return
|
|
32
|
+
return readStrategyFile(this.getStrategyPath(name));
|
|
35
33
|
}
|
|
36
34
|
listInstalledStrategyNames() {
|
|
37
35
|
const config = this.configService.readConfigOrDefault();
|
|
38
|
-
return
|
|
36
|
+
return listStrategyNamesFromConfig(config);
|
|
39
37
|
}
|
|
40
38
|
async addStrategy(input) {
|
|
41
39
|
this.configService.ensureConfigFileExists();
|
|
@@ -43,7 +41,7 @@ class StrategyService {
|
|
|
43
41
|
const sourceTrimmed = input.source.trim();
|
|
44
42
|
const isPinnedNpm = (() => {
|
|
45
43
|
try {
|
|
46
|
-
|
|
44
|
+
parsePinnedSpec(sourceTrimmed);
|
|
47
45
|
return true;
|
|
48
46
|
}
|
|
49
47
|
catch {
|
|
@@ -53,29 +51,29 @@ class StrategyService {
|
|
|
53
51
|
if (isPinnedNpm) {
|
|
54
52
|
const pinned = this.packStore.installPinned(sourceTrimmed);
|
|
55
53
|
const packRoot = this.packStore.resolvePackRoot(pinned.name);
|
|
56
|
-
const strategies =
|
|
54
|
+
const strategies = readStrategiesFromPackRoot(packRoot);
|
|
57
55
|
if (strategies.length === 0) {
|
|
58
56
|
throw new Error(`No *.strategy.json found in package: ${pinned.spec}`);
|
|
59
57
|
}
|
|
60
|
-
const allUnits = strategies.flatMap((s) =>
|
|
61
|
-
const refs =
|
|
62
|
-
|
|
58
|
+
const allUnits = strategies.flatMap((s) => normalizeStrategyUnits(s));
|
|
59
|
+
const refs = uniq(allUnits.map((u) => u.ref));
|
|
60
|
+
ensureNoInstalledVersionConflicts(this.unitStore.readInstalledDependencyVersions(), refs);
|
|
63
61
|
refs.forEach((ref) => this.unitStore.installPinned(ref));
|
|
64
62
|
const installedStrategies = [];
|
|
65
63
|
strategies.forEach((strategy) => {
|
|
66
64
|
const name = strategy.name;
|
|
67
65
|
const strategyPath = this.getStrategyPath(name);
|
|
68
|
-
if (!input.force &&
|
|
66
|
+
if (!input.force && fs.existsSync(strategyPath)) {
|
|
69
67
|
return;
|
|
70
68
|
}
|
|
71
69
|
const toWrite = {
|
|
72
70
|
...strategy,
|
|
73
71
|
source: { type: "npm", value: pinned.spec }
|
|
74
72
|
};
|
|
75
|
-
|
|
73
|
+
writeJsonAtomic(strategyPath, toWrite);
|
|
76
74
|
installedStrategies.push(name);
|
|
77
75
|
});
|
|
78
|
-
const nextConfig = installedStrategies.reduce((acc, s) =>
|
|
76
|
+
const nextConfig = installedStrategies.reduce((acc, s) => withStrategyInConfig(acc, s), config);
|
|
79
77
|
const existing = nextConfig.strategyPacks ?? [];
|
|
80
78
|
const filtered = existing.filter((x) => {
|
|
81
79
|
if (typeof x === "string") {
|
|
@@ -93,60 +91,60 @@ class StrategyService {
|
|
|
93
91
|
this.configService.writeConfig({ ...nextConfig, strategyPacks });
|
|
94
92
|
return { installedRefs: refs, installedStrategies };
|
|
95
93
|
}
|
|
96
|
-
const { strategy, source } = await
|
|
97
|
-
const normalizedUnits =
|
|
98
|
-
const refs =
|
|
94
|
+
const { strategy, source } = await fetchStrategyFile(sourceTrimmed);
|
|
95
|
+
const normalizedUnits = normalizeStrategyUnits(strategy);
|
|
96
|
+
const refs = uniq(normalizedUnits.map((u) => u.ref));
|
|
99
97
|
const strategyPath = this.getStrategyPath(strategy.name);
|
|
100
|
-
if (!input.force &&
|
|
98
|
+
if (!input.force && fs.existsSync(strategyPath)) {
|
|
101
99
|
throw new Error(`Strategy already exists: ${strategy.name}`);
|
|
102
100
|
}
|
|
103
|
-
|
|
101
|
+
ensureNoInstalledVersionConflicts(this.unitStore.readInstalledDependencyVersions(), refs);
|
|
104
102
|
refs.forEach((ref) => this.unitStore.installPinned(ref));
|
|
105
103
|
const toWrite = {
|
|
106
104
|
...strategy,
|
|
107
105
|
source
|
|
108
106
|
};
|
|
109
|
-
|
|
110
|
-
this.configService.writeConfig(
|
|
107
|
+
writeJsonAtomic(strategyPath, toWrite);
|
|
108
|
+
this.configService.writeConfig(withStrategyInConfig(config, strategy.name));
|
|
111
109
|
return { installedRefs: refs, installedStrategies: [strategy.name] };
|
|
112
110
|
}
|
|
113
111
|
removeStrategy(name) {
|
|
114
112
|
this.configService.ensureConfigFileExists();
|
|
115
113
|
const config = this.configService.readConfigOrDefault();
|
|
116
|
-
|
|
114
|
+
requireStrategyInstalled(config, name);
|
|
117
115
|
const strategyPath = this.getStrategyPath(name);
|
|
118
|
-
if (
|
|
119
|
-
|
|
116
|
+
if (fs.existsSync(strategyPath)) {
|
|
117
|
+
fs.rmSync(strategyPath);
|
|
120
118
|
}
|
|
121
|
-
this.configService.writeConfig(
|
|
119
|
+
this.configService.writeConfig(withoutStrategyInConfig(config, name));
|
|
122
120
|
}
|
|
123
121
|
async updateStrategy(name, opts) {
|
|
124
122
|
this.configService.ensureConfigFileExists();
|
|
125
123
|
const config = this.configService.readConfigOrDefault();
|
|
126
|
-
const names = name ? [name] :
|
|
124
|
+
const names = name ? [name] : listStrategyNamesFromConfig(config);
|
|
127
125
|
if (names.length === 0) {
|
|
128
126
|
return;
|
|
129
127
|
}
|
|
130
128
|
const installed = this.unitStore.readInstalledDependencyVersions();
|
|
131
129
|
if (opts?.packageOnly) {
|
|
132
|
-
const packageNames =
|
|
133
|
-
.flatMap((n) =>
|
|
130
|
+
const packageNames = uniq(names
|
|
131
|
+
.flatMap((n) => normalizeStrategyUnits(this.readStrategy(n)).map((u) => parsePinnedSpec(u.ref).name))
|
|
134
132
|
.filter((x) => x.trim().length > 0));
|
|
135
133
|
packageNames.forEach((pkg) => {
|
|
136
134
|
this.unitStore.updateToLatest(pkg);
|
|
137
135
|
});
|
|
138
136
|
const afterInstalled = this.unitStore.readInstalledDependencyVersions();
|
|
139
|
-
const afterStrategies =
|
|
137
|
+
const afterStrategies = listStrategyNamesFromConfig(config).map((n) => ({
|
|
140
138
|
name: n,
|
|
141
|
-
units:
|
|
139
|
+
units: normalizeStrategyUnits(this.readStrategy(n))
|
|
142
140
|
}));
|
|
143
|
-
const wanted =
|
|
144
|
-
const conflicts =
|
|
141
|
+
const wanted = collectWantedVersions(afterStrategies);
|
|
142
|
+
const conflicts = computeConflicts(wanted);
|
|
145
143
|
if (conflicts.length > 0) {
|
|
146
144
|
throw new Error(`Strategy version conflicts detected (${conflicts.length}). Run 'pp strategy doctor' for details.`);
|
|
147
145
|
}
|
|
148
146
|
const drift = afterStrategies
|
|
149
|
-
.map((s) =>
|
|
147
|
+
.map((s) => computeDrift({ strategyName: s.name, units: s.units, installed: afterInstalled }))
|
|
150
148
|
.filter((d) => d.items.length > 0);
|
|
151
149
|
if (drift.length > 0) {
|
|
152
150
|
throw new Error("Strategy drift detected after --package-only update. Run 'pp strategy doctor'.");
|
|
@@ -165,23 +163,23 @@ class StrategyService {
|
|
|
165
163
|
npmPacksToUpdate.add(src.value);
|
|
166
164
|
continue;
|
|
167
165
|
}
|
|
168
|
-
const fetched = await
|
|
166
|
+
const fetched = await fetchStrategyFile(src.value);
|
|
169
167
|
if (fetched.strategy.name !== n) {
|
|
170
168
|
throw new Error(`Strategy name mismatch while updating ${n}: got ${fetched.strategy.name}`);
|
|
171
169
|
}
|
|
172
170
|
const file = { ...fetched.strategy, source: fetched.source };
|
|
173
|
-
const units =
|
|
171
|
+
const units = normalizeStrategyUnits(file);
|
|
174
172
|
nextStrategies.push({ name: n, file, units });
|
|
175
173
|
}
|
|
176
174
|
if (npmPacksToUpdate.size > 0) {
|
|
177
175
|
const specs = [...npmPacksToUpdate];
|
|
178
176
|
specs.forEach((spec) => {
|
|
179
|
-
const pinned =
|
|
177
|
+
const pinned = parsePinnedSpec(spec);
|
|
180
178
|
this.packStore.updateToLatest(pinned.name);
|
|
181
179
|
});
|
|
182
180
|
const installedPackVersions = this.packStore.readInstalledDependencyVersions();
|
|
183
181
|
const updatedSpecs = specs.map((spec) => {
|
|
184
|
-
const pinned =
|
|
182
|
+
const pinned = parsePinnedSpec(spec);
|
|
185
183
|
const v = installedPackVersions[pinned.name];
|
|
186
184
|
if (!v) {
|
|
187
185
|
throw new Error(`Strategy pack not installed after update: ${pinned.name}`);
|
|
@@ -190,16 +188,16 @@ class StrategyService {
|
|
|
190
188
|
});
|
|
191
189
|
const updatedFiles = [];
|
|
192
190
|
updatedSpecs.forEach((spec) => {
|
|
193
|
-
const pinned =
|
|
191
|
+
const pinned = parsePinnedSpec(spec);
|
|
194
192
|
const root = this.packStore.resolvePackRoot(pinned.name);
|
|
195
|
-
const files =
|
|
193
|
+
const files = readStrategiesFromPackRoot(root).map((f) => ({
|
|
196
194
|
...f,
|
|
197
195
|
source: { type: "npm", value: spec }
|
|
198
196
|
}));
|
|
199
197
|
updatedFiles.push(...files);
|
|
200
198
|
});
|
|
201
199
|
updatedFiles.forEach((file) => {
|
|
202
|
-
const units =
|
|
200
|
+
const units = normalizeStrategyUnits(file);
|
|
203
201
|
nextStrategies.push({ name: file.name, file, units });
|
|
204
202
|
});
|
|
205
203
|
const nextConfig = this.configService.readConfigOrDefault();
|
|
@@ -208,48 +206,48 @@ class StrategyService {
|
|
|
208
206
|
if (typeof x === "string") {
|
|
209
207
|
return x.trim().length > 0;
|
|
210
208
|
}
|
|
211
|
-
return !updatedSpecs.some((spec) =>
|
|
209
|
+
return !updatedSpecs.some((spec) => parsePinnedSpec(spec).name === x.name);
|
|
212
210
|
});
|
|
213
211
|
const packsNext = [
|
|
214
212
|
...filtered,
|
|
215
213
|
...updatedSpecs.map((spec) => {
|
|
216
|
-
const pinned =
|
|
214
|
+
const pinned = parsePinnedSpec(spec);
|
|
217
215
|
return { name: pinned.name, spec: pinned.spec };
|
|
218
216
|
})
|
|
219
217
|
];
|
|
220
218
|
this.configService.writeConfig({ ...nextConfig, strategyPacks: packsNext });
|
|
221
219
|
}
|
|
222
|
-
const allOtherNames =
|
|
220
|
+
const allOtherNames = listStrategyNamesFromConfig(config).filter((n) => !names.includes(n));
|
|
223
221
|
const otherStrategies = allOtherNames.map((n) => ({
|
|
224
222
|
name: n,
|
|
225
|
-
units:
|
|
223
|
+
units: normalizeStrategyUnits(this.readStrategy(n))
|
|
226
224
|
}));
|
|
227
|
-
const wanted =
|
|
225
|
+
const wanted = collectWantedVersions([
|
|
228
226
|
...otherStrategies,
|
|
229
227
|
...nextStrategies.map((s) => ({ name: s.name, units: s.units }))
|
|
230
228
|
]);
|
|
231
|
-
const conflicts =
|
|
229
|
+
const conflicts = computeConflicts(wanted);
|
|
232
230
|
if (conflicts.length > 0) {
|
|
233
231
|
throw new Error(`Strategy version conflicts detected (${conflicts.length}). Run 'pp strategy doctor' for details.`);
|
|
234
232
|
}
|
|
235
|
-
const refs =
|
|
236
|
-
|
|
233
|
+
const refs = uniq(nextStrategies.flatMap((s) => s.units.map((u) => u.ref)));
|
|
234
|
+
ensureNoInstalledVersionConflicts(installed, refs);
|
|
237
235
|
refs.forEach((ref) => {
|
|
238
236
|
this.unitStore.installPinned(ref);
|
|
239
237
|
});
|
|
240
238
|
nextStrategies.forEach((s) => {
|
|
241
|
-
|
|
239
|
+
writeJsonAtomic(this.getStrategyPath(s.name), s.file);
|
|
242
240
|
});
|
|
243
241
|
}
|
|
244
242
|
pinStrategy(name) {
|
|
245
243
|
this.configService.ensureConfigFileExists();
|
|
246
244
|
const config = this.configService.readConfigOrDefault();
|
|
247
|
-
|
|
245
|
+
requireStrategyInstalled(config, name);
|
|
248
246
|
const file = this.readStrategy(name);
|
|
249
|
-
const units =
|
|
247
|
+
const units = normalizeStrategyUnits(file);
|
|
250
248
|
const installed = this.unitStore.readInstalledDependencyVersions();
|
|
251
249
|
const pinnedUnits = units.map((u) => {
|
|
252
|
-
const pinned =
|
|
250
|
+
const pinned = parsePinnedSpec(u.ref);
|
|
253
251
|
const v = installed[pinned.name];
|
|
254
252
|
if (!v) {
|
|
255
253
|
throw new Error(`Unit package is not installed: ${pinned.name}`);
|
|
@@ -257,17 +255,17 @@ class StrategyService {
|
|
|
257
255
|
const nextRef = `${pinned.name}@${v}`;
|
|
258
256
|
return u.args.length === 0 ? nextRef : { ref: nextRef, args: u.args };
|
|
259
257
|
});
|
|
260
|
-
const others =
|
|
258
|
+
const others = listStrategyNamesFromConfig(config)
|
|
261
259
|
.filter((n) => n !== name)
|
|
262
|
-
.map((n) => ({ name: n, units:
|
|
263
|
-
const nextWanted =
|
|
260
|
+
.map((n) => ({ name: n, units: normalizeStrategyUnits(this.readStrategy(n)) }));
|
|
261
|
+
const nextWanted = collectWantedVersions([
|
|
264
262
|
...others,
|
|
265
263
|
{
|
|
266
264
|
name,
|
|
267
265
|
units: pinnedUnits.map((x) => typeof x === "string" ? { ref: x, args: [] } : { ref: x.ref, args: x.args ?? [] })
|
|
268
266
|
}
|
|
269
267
|
]);
|
|
270
|
-
const conflicts =
|
|
268
|
+
const conflicts = computeConflicts(nextWanted);
|
|
271
269
|
if (conflicts.length > 0) {
|
|
272
270
|
throw new Error(`Strategy version conflicts detected (${conflicts.length}). Run 'pp strategy doctor' for details.`);
|
|
273
271
|
}
|
|
@@ -278,23 +276,22 @@ class StrategyService {
|
|
|
278
276
|
units: pinnedUnits
|
|
279
277
|
}
|
|
280
278
|
};
|
|
281
|
-
|
|
279
|
+
writeJsonAtomic(this.getStrategyPath(name), nextFile);
|
|
282
280
|
}
|
|
283
281
|
doctor() {
|
|
284
282
|
this.configService.ensureConfigFileExists();
|
|
285
283
|
const config = this.configService.readConfigOrDefault();
|
|
286
|
-
const names =
|
|
284
|
+
const names = listStrategyNamesFromConfig(config);
|
|
287
285
|
const installed = this.unitStore.readInstalledDependencyVersions();
|
|
288
286
|
const strategies = names.map((n) => ({
|
|
289
287
|
name: n,
|
|
290
|
-
units:
|
|
288
|
+
units: normalizeStrategyUnits(this.readStrategy(n))
|
|
291
289
|
}));
|
|
292
|
-
const wanted =
|
|
293
|
-
const conflicts =
|
|
290
|
+
const wanted = collectWantedVersions(strategies);
|
|
291
|
+
const conflicts = computeConflicts(wanted);
|
|
294
292
|
const drift = strategies
|
|
295
|
-
.map((s) =>
|
|
293
|
+
.map((s) => computeDrift({ strategyName: s.name, units: s.units, installed }))
|
|
296
294
|
.filter((d) => d.items.length > 0);
|
|
297
295
|
return { conflicts, drift };
|
|
298
296
|
}
|
|
299
297
|
}
|
|
300
|
-
exports.StrategyService = StrategyService;
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const parse_pinned_spec_1 = require("../user-packages/parse-pinned-spec");
|
|
6
|
-
const user_package_store_1 = require("../user-packages/user-package-store");
|
|
7
|
-
const unit_validate_1 = require("./unit-validate");
|
|
1
|
+
import { installPinnedPackage, updatePackageToLatest } from "../user-packages/user-package-installer.js";
|
|
2
|
+
import { parsePinnedSpec } from "../user-packages/parse-pinned-spec.js";
|
|
3
|
+
import { UserPackageStore } from "../user-packages/user-package-store.js";
|
|
4
|
+
import { isUnitLike, resolveUnitConstructor } from "./unit-validate.js";
|
|
8
5
|
const UNITS_KIND = "units";
|
|
9
6
|
const UNITS_PACKAGE_JSON_NAME = "pagepocket-user-units";
|
|
10
|
-
class UnitStore {
|
|
7
|
+
export class UnitStore {
|
|
8
|
+
store;
|
|
11
9
|
constructor(configService) {
|
|
12
|
-
this.store = new
|
|
10
|
+
this.store = new UserPackageStore(configService, UNITS_KIND);
|
|
13
11
|
}
|
|
14
12
|
getInstallDir() {
|
|
15
13
|
return this.store.getInstallDir();
|
|
@@ -21,14 +19,14 @@ class UnitStore {
|
|
|
21
19
|
return this.store.readInstalledPackageMeta(UNITS_PACKAGE_JSON_NAME, packageName);
|
|
22
20
|
}
|
|
23
21
|
installPinned(ref) {
|
|
24
|
-
const pinned =
|
|
25
|
-
|
|
22
|
+
const pinned = parsePinnedSpec(ref);
|
|
23
|
+
installPinnedPackage(this.store, {
|
|
26
24
|
packageJsonName: UNITS_PACKAGE_JSON_NAME,
|
|
27
25
|
packageSpec: pinned.spec
|
|
28
26
|
});
|
|
29
27
|
}
|
|
30
28
|
updateToLatest(packageName) {
|
|
31
|
-
|
|
29
|
+
updatePackageToLatest(this.store, {
|
|
32
30
|
packageJsonName: UNITS_PACKAGE_JSON_NAME,
|
|
33
31
|
packageName
|
|
34
32
|
});
|
|
@@ -37,18 +35,16 @@ class UnitStore {
|
|
|
37
35
|
return this.store.importModule(UNITS_PACKAGE_JSON_NAME, packageName);
|
|
38
36
|
}
|
|
39
37
|
async instantiateFromRef(ref, args) {
|
|
40
|
-
const pinned =
|
|
38
|
+
const pinned = parsePinnedSpec(ref);
|
|
41
39
|
const mod = await this.importUnitModule(pinned.name);
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
throw new Error(`Unit ${pinned.name}
|
|
40
|
+
const ctor = resolveUnitConstructor(mod);
|
|
41
|
+
if (!ctor) {
|
|
42
|
+
throw new Error(`Unit ${pinned.name} does not export a Unit constructor.`);
|
|
45
43
|
}
|
|
46
|
-
const ctor = def;
|
|
47
44
|
const instance = new ctor(...args);
|
|
48
|
-
if (!
|
|
49
|
-
throw new Error(`Unit ${pinned.name}
|
|
45
|
+
if (!isUnitLike(instance)) {
|
|
46
|
+
throw new Error(`Unit ${pinned.name} exported constructor did not produce a Unit.`);
|
|
50
47
|
}
|
|
51
48
|
return instance;
|
|
52
49
|
}
|
|
53
50
|
}
|
|
54
|
-
exports.UnitStore = UnitStore;
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isUnitLike = void 0;
|
|
4
1
|
const isRecord = (value) => {
|
|
5
2
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6
3
|
};
|
|
7
4
|
const isCallable = (value) => {
|
|
8
5
|
return typeof value === "function";
|
|
9
6
|
};
|
|
10
|
-
const isUnitLike = (value) => {
|
|
7
|
+
export const isUnitLike = (value) => {
|
|
11
8
|
if (!isRecord(value)) {
|
|
12
9
|
return false;
|
|
13
10
|
}
|
|
@@ -25,4 +22,22 @@ const isUnitLike = (value) => {
|
|
|
25
22
|
}
|
|
26
23
|
return true;
|
|
27
24
|
};
|
|
28
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a Unit constructor from an imported ESM module.
|
|
27
|
+
*
|
|
28
|
+
* Checks `mod.default` first. If it is not a function (e.g. older packages
|
|
29
|
+
* that only use named exports), falls back to the first exported function
|
|
30
|
+
* whose name ends with "Unit".
|
|
31
|
+
*
|
|
32
|
+
* @returns The constructor function, or `undefined` when none is found.
|
|
33
|
+
*/
|
|
34
|
+
export const resolveUnitConstructor = (mod) => {
|
|
35
|
+
if (typeof mod.default === "function") {
|
|
36
|
+
return mod.default;
|
|
37
|
+
}
|
|
38
|
+
const fallback = Object.values(mod).find((v) => typeof v === "function" && v.name?.endsWith("Unit"));
|
|
39
|
+
if (typeof fallback === "function") {
|
|
40
|
+
return fallback;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
};
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
4
|
const isRecord = (value) => {
|
|
5
5
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6
6
|
};
|
|
7
7
|
const isExactSemver = (value) => {
|
|
8
8
|
return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(value);
|
|
9
9
|
};
|
|
10
|
-
const parsePinnedSpec = (input) => {
|
|
10
|
+
export const parsePinnedSpec = (input) => {
|
|
11
11
|
const trimmed = input.trim();
|
|
12
12
|
if (!trimmed) {
|
|
13
13
|
throw new Error("package spec is empty");
|
|
14
14
|
}
|
|
15
|
-
const
|
|
15
|
+
const req = createRequire(import.meta.url ? fileURLToPath(import.meta.url) : path.join(process.cwd(), "package.json"));
|
|
16
|
+
const npa = req("npm-package-arg");
|
|
16
17
|
const parsed = npa(trimmed);
|
|
17
18
|
if (!isRecord(parsed)) {
|
|
18
19
|
throw new Error(`Invalid package spec: ${trimmed}`);
|
|
@@ -34,4 +35,3 @@ const parsePinnedSpec = (input) => {
|
|
|
34
35
|
}
|
|
35
36
|
return { name, version: rawSpec, spec: `${name}@${rawSpec}` };
|
|
36
37
|
};
|
|
37
|
-
exports.parsePinnedSpec = parsePinnedSpec;
|