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