@pagepocket/cli 0.9.2 → 0.11.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 +188 -96
- package/dist/commands/strategy/add.js +34 -0
- package/dist/commands/strategy/doctor.js +34 -0
- package/dist/commands/strategy/ls.js +25 -0
- package/dist/commands/strategy/pin.js +21 -0
- package/dist/commands/strategy/remove.js +21 -0
- package/dist/commands/strategy/update.js +34 -0
- package/dist/services/config-service.js +54 -22
- package/dist/services/strategy/builtin-strategy-registry.js +32 -0
- package/dist/services/strategy/strategy-analyze.js +67 -0
- package/dist/services/strategy/strategy-config.js +25 -0
- package/dist/services/strategy/strategy-fetch.js +71 -0
- package/dist/services/strategy/strategy-io.js +59 -0
- package/dist/services/strategy/strategy-normalize.js +51 -0
- package/dist/services/strategy/strategy-pack-read.js +54 -0
- package/dist/services/strategy/strategy-pack-store.js +45 -0
- package/dist/services/strategy/strategy-service.js +301 -0
- package/dist/services/strategy/types.js +2 -0
- package/dist/services/units/unit-store.js +54 -0
- package/dist/services/units/unit-validate.js +28 -0
- package/dist/services/user-packages/parse-pinned-spec.js +43 -0
- package/dist/services/user-packages/user-package-installer.js +47 -0
- package/dist/services/user-packages/user-package-store.js +126 -0
- package/dist/utils/array.js +7 -0
- package/dist/utils/parse-plugin-spec.js +7 -1
- package/package.json +11 -10
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureNoInstalledVersionConflicts = exports.computeDrift = exports.computeConflicts = exports.collectWantedVersions = void 0;
|
|
4
|
+
const parse_pinned_spec_1 = require("../user-packages/parse-pinned-spec");
|
|
5
|
+
const collectWantedVersions = (strategies) => {
|
|
6
|
+
var _a, _b, _c;
|
|
7
|
+
const wanted = {};
|
|
8
|
+
for (const s of strategies) {
|
|
9
|
+
for (const u of s.units) {
|
|
10
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(u.ref);
|
|
11
|
+
wanted[_a = pinned.name] ?? (wanted[_a] = {});
|
|
12
|
+
(_b = wanted[pinned.name])[_c = pinned.version] ?? (_b[_c] = []);
|
|
13
|
+
wanted[pinned.name][pinned.version].push(s.name);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return wanted;
|
|
17
|
+
};
|
|
18
|
+
exports.collectWantedVersions = collectWantedVersions;
|
|
19
|
+
const computeConflicts = (wanted) => {
|
|
20
|
+
const conflicts = [];
|
|
21
|
+
for (const [name, versions] of Object.entries(wanted)) {
|
|
22
|
+
const uniqueVersions = Object.keys(versions);
|
|
23
|
+
if (uniqueVersions.length <= 1) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
conflicts.push({ name, versions });
|
|
27
|
+
}
|
|
28
|
+
return conflicts;
|
|
29
|
+
};
|
|
30
|
+
exports.computeConflicts = computeConflicts;
|
|
31
|
+
const computeDrift = (input) => {
|
|
32
|
+
const items = input.units.flatMap((u) => {
|
|
33
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(u.ref);
|
|
34
|
+
const installedVersion = input.installed[pinned.name];
|
|
35
|
+
if (!installedVersion || installedVersion !== pinned.version) {
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
name: pinned.name,
|
|
39
|
+
pinned: pinned.version,
|
|
40
|
+
installed: installedVersion
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
return [];
|
|
45
|
+
});
|
|
46
|
+
return { strategy: input.strategyName, items };
|
|
47
|
+
};
|
|
48
|
+
exports.computeDrift = computeDrift;
|
|
49
|
+
const ensureNoInstalledVersionConflicts = (installed, refs) => {
|
|
50
|
+
const byName = {};
|
|
51
|
+
for (const ref of refs) {
|
|
52
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(ref);
|
|
53
|
+
const already = byName[pinned.name];
|
|
54
|
+
if (already && already !== pinned.version) {
|
|
55
|
+
throw new Error(`Strategy contains conflicting versions for ${pinned.name}: ${already} vs ${pinned.version}`);
|
|
56
|
+
}
|
|
57
|
+
byName[pinned.name] = pinned.version;
|
|
58
|
+
}
|
|
59
|
+
for (const [name, version] of Object.entries(byName)) {
|
|
60
|
+
const installedVersion = installed[name];
|
|
61
|
+
if (installedVersion && installedVersion !== version) {
|
|
62
|
+
throw new Error(`Version conflict for ${name}: installed ${installedVersion}, strategy requires ${version}. ` +
|
|
63
|
+
"This CLI stores only one version per package name.");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
exports.ensureNoInstalledVersionConflicts = ensureNoInstalledVersionConflicts;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withoutStrategyInConfig = exports.withStrategyInConfig = exports.requireStrategyInstalled = exports.listStrategyNamesFromConfig = void 0;
|
|
4
|
+
const array_1 = require("../../utils/array");
|
|
5
|
+
const listStrategyNamesFromConfig = (config) => {
|
|
6
|
+
return (0, array_1.uniq)((config.strategies ?? []).map((s) => s.trim()).filter((s) => s.length > 0));
|
|
7
|
+
};
|
|
8
|
+
exports.listStrategyNamesFromConfig = listStrategyNamesFromConfig;
|
|
9
|
+
const requireStrategyInstalled = (config, name) => {
|
|
10
|
+
const installed = (0, exports.listStrategyNamesFromConfig)(config);
|
|
11
|
+
if (!installed.includes(name)) {
|
|
12
|
+
throw new Error(`Strategy not installed: ${name}`);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
exports.requireStrategyInstalled = requireStrategyInstalled;
|
|
16
|
+
const withStrategyInConfig = (config, name) => {
|
|
17
|
+
const strategies = (0, array_1.uniq)([...(config.strategies ?? []), name]);
|
|
18
|
+
return { ...config, strategies };
|
|
19
|
+
};
|
|
20
|
+
exports.withStrategyInConfig = withStrategyInConfig;
|
|
21
|
+
const withoutStrategyInConfig = (config, name) => {
|
|
22
|
+
const strategies = (config.strategies ?? []).filter((s) => s !== name);
|
|
23
|
+
return { ...config, strategies };
|
|
24
|
+
};
|
|
25
|
+
exports.withoutStrategyInConfig = withoutStrategyInConfig;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.fetchStrategyFile = 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");
|
|
10
|
+
const normalize_argv_1 = require("../../utils/normalize-argv");
|
|
11
|
+
const isRecord = (value) => {
|
|
12
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
13
|
+
};
|
|
14
|
+
const isStrategyFile = (value) => {
|
|
15
|
+
if (!isRecord(value)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (value.schemaVersion !== 1) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (typeof value.name !== "string" || value.name.trim().length === 0) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const pipeline = value.pipeline;
|
|
25
|
+
if (!isRecord(pipeline)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (!Array.isArray(pipeline.units)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
};
|
|
33
|
+
const readTextFromPath = (p) => {
|
|
34
|
+
const full = node_path_1.default.resolve(p);
|
|
35
|
+
return node_fs_1.default.readFileSync(full, "utf8");
|
|
36
|
+
};
|
|
37
|
+
const fetchStrategyFile = async (input) => {
|
|
38
|
+
const raw = input.trim();
|
|
39
|
+
if (!raw) {
|
|
40
|
+
throw new Error("strategy source is empty");
|
|
41
|
+
}
|
|
42
|
+
if ((0, normalize_argv_1.isUrlLike)(raw)) {
|
|
43
|
+
const url = new URL(raw);
|
|
44
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
45
|
+
throw new Error(`Unsupported strategy URL protocol: ${url.protocol}`);
|
|
46
|
+
}
|
|
47
|
+
const res = await fetch(url.toString());
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
throw new Error(`Failed to fetch strategy: ${res.status} ${res.statusText}`);
|
|
50
|
+
}
|
|
51
|
+
const text = await res.text();
|
|
52
|
+
const parsed = (0, parse_json_1.parseJson)(text);
|
|
53
|
+
if (!parsed.ok) {
|
|
54
|
+
throw parsed.error;
|
|
55
|
+
}
|
|
56
|
+
if (!isStrategyFile(parsed.value)) {
|
|
57
|
+
throw new Error(`Invalid strategy JSON from URL: ${url.toString()}`);
|
|
58
|
+
}
|
|
59
|
+
return { strategy: parsed.value, source: { type: "url", value: url.toString() } };
|
|
60
|
+
}
|
|
61
|
+
const text = readTextFromPath(raw);
|
|
62
|
+
const parsed = (0, parse_json_1.parseJson)(text);
|
|
63
|
+
if (!parsed.ok) {
|
|
64
|
+
throw parsed.error;
|
|
65
|
+
}
|
|
66
|
+
if (!isStrategyFile(parsed.value)) {
|
|
67
|
+
throw new Error(`Invalid strategy JSON from file: ${raw}`);
|
|
68
|
+
}
|
|
69
|
+
return { strategy: parsed.value, source: { type: "file", value: node_path_1.default.resolve(raw) } };
|
|
70
|
+
};
|
|
71
|
+
exports.fetchStrategyFile = fetchStrategyFile;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.writeJsonAtomic = exports.readStrategyFile = exports.getStrategyPath = exports.getStrategiesDir = 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");
|
|
10
|
+
const isRecord = (value) => {
|
|
11
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
12
|
+
};
|
|
13
|
+
const isStrategyFile = (value) => {
|
|
14
|
+
if (!isRecord(value)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (value.schemaVersion !== 1) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (typeof value.name !== "string" || value.name.trim().length === 0) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const pipeline = value.pipeline;
|
|
24
|
+
if (!isRecord(pipeline)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (!Array.isArray(pipeline.units)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
};
|
|
32
|
+
const getStrategiesDir = (configService) => {
|
|
33
|
+
return node_path_1.default.join(configService.getConfigDir(), "strategies");
|
|
34
|
+
};
|
|
35
|
+
exports.getStrategiesDir = getStrategiesDir;
|
|
36
|
+
const getStrategyPath = (configService, name) => {
|
|
37
|
+
return node_path_1.default.join((0, exports.getStrategiesDir)(configService), `${name}.json`);
|
|
38
|
+
};
|
|
39
|
+
exports.getStrategyPath = getStrategyPath;
|
|
40
|
+
const readStrategyFile = (filePath) => {
|
|
41
|
+
const text = node_fs_1.default.readFileSync(filePath, "utf8");
|
|
42
|
+
const parsed = (0, parse_json_1.parseJson)(text);
|
|
43
|
+
if (!parsed.ok) {
|
|
44
|
+
throw parsed.error;
|
|
45
|
+
}
|
|
46
|
+
if (!isStrategyFile(parsed.value)) {
|
|
47
|
+
throw new Error(`Invalid strategy file: ${filePath}`);
|
|
48
|
+
}
|
|
49
|
+
return parsed.value;
|
|
50
|
+
};
|
|
51
|
+
exports.readStrategyFile = readStrategyFile;
|
|
52
|
+
const writeJsonAtomic = (filePath, value) => {
|
|
53
|
+
const dir = node_path_1.default.dirname(filePath);
|
|
54
|
+
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
55
|
+
const tmpPath = `${filePath}.tmp`;
|
|
56
|
+
node_fs_1.default.writeFileSync(tmpPath, `${JSON.stringify(value, undefined, 2)}\n`, "utf8");
|
|
57
|
+
node_fs_1.default.renameSync(tmpPath, filePath);
|
|
58
|
+
};
|
|
59
|
+
exports.writeJsonAtomic = writeJsonAtomic;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeStrategyUnits = void 0;
|
|
4
|
+
const parse_pinned_spec_1 = require("../user-packages/parse-pinned-spec");
|
|
5
|
+
const isRecord = (value) => {
|
|
6
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7
|
+
};
|
|
8
|
+
const isJsonValue = (value) => {
|
|
9
|
+
if (value === null) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
const t = typeof value;
|
|
13
|
+
if (t === "string" || t === "number" || t === "boolean") {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return value.every((v) => isJsonValue(v));
|
|
18
|
+
}
|
|
19
|
+
if (isRecord(value)) {
|
|
20
|
+
return Object.values(value).every((v) => isJsonValue(v));
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
};
|
|
24
|
+
const normalizeUnitSpec = (spec, idx) => {
|
|
25
|
+
if (typeof spec === "string") {
|
|
26
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(spec);
|
|
27
|
+
return { ref: pinned.spec, args: [] };
|
|
28
|
+
}
|
|
29
|
+
if (!isRecord(spec)) {
|
|
30
|
+
throw new Error(`Invalid unit spec at index ${idx}`);
|
|
31
|
+
}
|
|
32
|
+
const ref = spec.ref;
|
|
33
|
+
if (typeof ref !== "string") {
|
|
34
|
+
throw new Error(`Invalid unit ref at index ${idx}`);
|
|
35
|
+
}
|
|
36
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(ref);
|
|
37
|
+
const argsRaw = spec.args;
|
|
38
|
+
const args = typeof argsRaw === "undefined" ? [] : argsRaw;
|
|
39
|
+
if (!Array.isArray(args)) {
|
|
40
|
+
throw new Error(`Invalid unit args at index ${idx} (must be an array)`);
|
|
41
|
+
}
|
|
42
|
+
if (!isJsonValue(args)) {
|
|
43
|
+
throw new Error(`Invalid unit args at index ${idx} (must be JSON-only)`);
|
|
44
|
+
}
|
|
45
|
+
return { ref: pinned.spec, args };
|
|
46
|
+
};
|
|
47
|
+
const normalizeStrategyUnits = (strategy) => {
|
|
48
|
+
const units = strategy.pipeline.units;
|
|
49
|
+
return units.map((spec, idx) => normalizeUnitSpec(spec, idx));
|
|
50
|
+
};
|
|
51
|
+
exports.normalizeStrategyUnits = normalizeStrategyUnits;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
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");
|
|
10
|
+
const isRecord = (value) => {
|
|
11
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
12
|
+
};
|
|
13
|
+
const isStrategyFile = (value) => {
|
|
14
|
+
if (!isRecord(value)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (value.schemaVersion !== 1) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (typeof value.name !== "string" || value.name.trim().length === 0) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const pipeline = value.pipeline;
|
|
24
|
+
if (!isRecord(pipeline)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (!Array.isArray(pipeline.units)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
};
|
|
32
|
+
const readStrategiesFromPackRoot = (packRoot) => {
|
|
33
|
+
const strategiesDir = node_path_1.default.join(packRoot, "strategies");
|
|
34
|
+
if (!node_fs_1.default.existsSync(strategiesDir)) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const fileNames = node_fs_1.default
|
|
38
|
+
.readdirSync(strategiesDir)
|
|
39
|
+
.filter((f) => f.endsWith(".strategy.json"))
|
|
40
|
+
.sort((a, b) => a.localeCompare(b));
|
|
41
|
+
return fileNames.map((fileName) => {
|
|
42
|
+
const filePath = node_path_1.default.join(strategiesDir, fileName);
|
|
43
|
+
const text = node_fs_1.default.readFileSync(filePath, "utf8");
|
|
44
|
+
const parsed = (0, parse_json_1.parseJson)(text);
|
|
45
|
+
if (!parsed.ok) {
|
|
46
|
+
throw parsed.error;
|
|
47
|
+
}
|
|
48
|
+
if (!isStrategyFile(parsed.value)) {
|
|
49
|
+
throw new Error(`Invalid strategy file in pack: ${filePath}`);
|
|
50
|
+
}
|
|
51
|
+
return parsed.value;
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
exports.readStrategiesFromPackRoot = readStrategiesFromPackRoot;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
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");
|
|
12
|
+
const STRATEGY_PACKS_KIND = "strategy-packs";
|
|
13
|
+
const STRATEGY_PACKS_PACKAGE_JSON_NAME = "pagepocket-user-strategy-packs";
|
|
14
|
+
class StrategyPackStore {
|
|
15
|
+
constructor(configService) {
|
|
16
|
+
this.store = new user_package_store_1.UserPackageStore(configService, STRATEGY_PACKS_KIND);
|
|
17
|
+
}
|
|
18
|
+
getInstallDir() {
|
|
19
|
+
return this.store.getInstallDir();
|
|
20
|
+
}
|
|
21
|
+
readInstalledDependencyVersions() {
|
|
22
|
+
return this.store.readInstalledDependencyVersions(STRATEGY_PACKS_PACKAGE_JSON_NAME);
|
|
23
|
+
}
|
|
24
|
+
installPinned(spec) {
|
|
25
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(spec);
|
|
26
|
+
(0, user_package_installer_1.installPinnedPackage)(this.store, {
|
|
27
|
+
packageJsonName: STRATEGY_PACKS_PACKAGE_JSON_NAME,
|
|
28
|
+
packageSpec: pinned.spec
|
|
29
|
+
});
|
|
30
|
+
return pinned;
|
|
31
|
+
}
|
|
32
|
+
updateToLatest(packageName) {
|
|
33
|
+
(0, user_package_installer_2.updatePackageToLatest)(this.store, {
|
|
34
|
+
packageJsonName: STRATEGY_PACKS_PACKAGE_JSON_NAME,
|
|
35
|
+
packageName
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
resolvePackRoot(packageName) {
|
|
39
|
+
this.store.ensureInstallDirPackageJson(STRATEGY_PACKS_PACKAGE_JSON_NAME);
|
|
40
|
+
const req = this.store.createRequire(STRATEGY_PACKS_PACKAGE_JSON_NAME);
|
|
41
|
+
const pkgJsonPath = req.resolve(`${packageName}/package.json`);
|
|
42
|
+
return node_path_1.default.dirname(pkgJsonPath);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.StrategyPackStore = StrategyPackStore;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.StrategyService = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const config_service_1 = require("../config-service");
|
|
9
|
+
const parse_pinned_spec_1 = require("../user-packages/parse-pinned-spec");
|
|
10
|
+
const unit_store_1 = require("../units/unit-store");
|
|
11
|
+
const array_1 = require("../../utils/array");
|
|
12
|
+
const strategy_analyze_1 = require("./strategy-analyze");
|
|
13
|
+
const strategy_config_1 = require("./strategy-config");
|
|
14
|
+
const strategy_fetch_1 = require("./strategy-fetch");
|
|
15
|
+
const strategy_io_1 = require("./strategy-io");
|
|
16
|
+
const strategy_normalize_1 = require("./strategy-normalize");
|
|
17
|
+
const strategy_pack_read_1 = require("./strategy-pack-read");
|
|
18
|
+
const strategy_pack_store_1 = require("./strategy-pack-store");
|
|
19
|
+
class StrategyService {
|
|
20
|
+
constructor(configService = new config_service_1.ConfigService()) {
|
|
21
|
+
this.configService = configService;
|
|
22
|
+
this.unitStore = new unit_store_1.UnitStore(configService);
|
|
23
|
+
this.packStore = new strategy_pack_store_1.StrategyPackStore(configService);
|
|
24
|
+
}
|
|
25
|
+
ensureConfigFileExists() {
|
|
26
|
+
return this.configService.ensureConfigFileExists();
|
|
27
|
+
}
|
|
28
|
+
getStrategiesDir() {
|
|
29
|
+
return (0, strategy_io_1.getStrategiesDir)(this.configService);
|
|
30
|
+
}
|
|
31
|
+
getStrategyPath(name) {
|
|
32
|
+
return (0, strategy_io_1.getStrategyPath)(this.configService, name);
|
|
33
|
+
}
|
|
34
|
+
readStrategy(name) {
|
|
35
|
+
return (0, strategy_io_1.readStrategyFile)(this.getStrategyPath(name));
|
|
36
|
+
}
|
|
37
|
+
listInstalledStrategyNames() {
|
|
38
|
+
const config = this.configService.readConfigOrDefault();
|
|
39
|
+
return (0, strategy_config_1.listStrategyNamesFromConfig)(config);
|
|
40
|
+
}
|
|
41
|
+
async addStrategy(input) {
|
|
42
|
+
this.configService.ensureConfigFileExists();
|
|
43
|
+
const config = this.configService.readConfigOrDefault();
|
|
44
|
+
const sourceTrimmed = input.source.trim();
|
|
45
|
+
const isPinnedNpm = (() => {
|
|
46
|
+
try {
|
|
47
|
+
(0, parse_pinned_spec_1.parsePinnedSpec)(sourceTrimmed);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
})();
|
|
54
|
+
if (isPinnedNpm) {
|
|
55
|
+
const pinned = this.packStore.installPinned(sourceTrimmed);
|
|
56
|
+
const packRoot = this.packStore.resolvePackRoot(pinned.name);
|
|
57
|
+
const strategies = (0, strategy_pack_read_1.readStrategiesFromPackRoot)(packRoot);
|
|
58
|
+
if (strategies.length === 0) {
|
|
59
|
+
throw new Error(`No *.strategy.json found in package: ${pinned.spec}`);
|
|
60
|
+
}
|
|
61
|
+
const allUnits = strategies.flatMap((s) => (0, strategy_normalize_1.normalizeStrategyUnits)(s));
|
|
62
|
+
const refs = (0, array_1.uniq)(allUnits.map((u) => u.ref));
|
|
63
|
+
(0, strategy_analyze_1.ensureNoInstalledVersionConflicts)(this.unitStore.readInstalledDependencyVersions(), refs);
|
|
64
|
+
refs.forEach((ref) => this.unitStore.installPinned(ref));
|
|
65
|
+
const installedStrategies = [];
|
|
66
|
+
strategies.forEach((strategy) => {
|
|
67
|
+
const name = strategy.name;
|
|
68
|
+
const strategyPath = this.getStrategyPath(name);
|
|
69
|
+
if (!input.force && node_fs_1.default.existsSync(strategyPath)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const toWrite = {
|
|
73
|
+
...strategy,
|
|
74
|
+
source: { type: "npm", value: pinned.spec }
|
|
75
|
+
};
|
|
76
|
+
(0, strategy_io_1.writeJsonAtomic)(strategyPath, toWrite);
|
|
77
|
+
installedStrategies.push(name);
|
|
78
|
+
});
|
|
79
|
+
const nextConfig = installedStrategies.reduce((acc, s) => (0, strategy_config_1.withStrategyInConfig)(acc, s), config);
|
|
80
|
+
const existing = nextConfig.strategyPacks ?? [];
|
|
81
|
+
const filtered = existing.filter((x) => {
|
|
82
|
+
if (typeof x === "string") {
|
|
83
|
+
return x.trim().length > 0;
|
|
84
|
+
}
|
|
85
|
+
return x.name !== pinned.name;
|
|
86
|
+
});
|
|
87
|
+
const strategyPacks = [
|
|
88
|
+
...filtered,
|
|
89
|
+
{
|
|
90
|
+
name: pinned.name,
|
|
91
|
+
spec: pinned.spec
|
|
92
|
+
}
|
|
93
|
+
];
|
|
94
|
+
this.configService.writeConfig({ ...nextConfig, strategyPacks });
|
|
95
|
+
return { installedRefs: refs, installedStrategies };
|
|
96
|
+
}
|
|
97
|
+
const { strategy, source } = await (0, strategy_fetch_1.fetchStrategyFile)(sourceTrimmed);
|
|
98
|
+
const normalizedUnits = (0, strategy_normalize_1.normalizeStrategyUnits)(strategy);
|
|
99
|
+
const refs = (0, array_1.uniq)(normalizedUnits.map((u) => u.ref));
|
|
100
|
+
const strategyPath = this.getStrategyPath(strategy.name);
|
|
101
|
+
if (!input.force && node_fs_1.default.existsSync(strategyPath)) {
|
|
102
|
+
throw new Error(`Strategy already exists: ${strategy.name}`);
|
|
103
|
+
}
|
|
104
|
+
(0, strategy_analyze_1.ensureNoInstalledVersionConflicts)(this.unitStore.readInstalledDependencyVersions(), refs);
|
|
105
|
+
refs.forEach((ref) => this.unitStore.installPinned(ref));
|
|
106
|
+
const toWrite = {
|
|
107
|
+
...strategy,
|
|
108
|
+
source
|
|
109
|
+
};
|
|
110
|
+
(0, strategy_io_1.writeJsonAtomic)(strategyPath, toWrite);
|
|
111
|
+
this.configService.writeConfig((0, strategy_config_1.withStrategyInConfig)(config, strategy.name));
|
|
112
|
+
return { installedRefs: refs, installedStrategies: [strategy.name] };
|
|
113
|
+
}
|
|
114
|
+
removeStrategy(name) {
|
|
115
|
+
this.configService.ensureConfigFileExists();
|
|
116
|
+
const config = this.configService.readConfigOrDefault();
|
|
117
|
+
(0, strategy_config_1.requireStrategyInstalled)(config, name);
|
|
118
|
+
const strategyPath = this.getStrategyPath(name);
|
|
119
|
+
if (node_fs_1.default.existsSync(strategyPath)) {
|
|
120
|
+
node_fs_1.default.rmSync(strategyPath);
|
|
121
|
+
}
|
|
122
|
+
this.configService.writeConfig((0, strategy_config_1.withoutStrategyInConfig)(config, name));
|
|
123
|
+
}
|
|
124
|
+
async updateStrategy(name, opts) {
|
|
125
|
+
this.configService.ensureConfigFileExists();
|
|
126
|
+
const config = this.configService.readConfigOrDefault();
|
|
127
|
+
const names = name ? [name] : (0, strategy_config_1.listStrategyNamesFromConfig)(config);
|
|
128
|
+
if (names.length === 0) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const installed = this.unitStore.readInstalledDependencyVersions();
|
|
132
|
+
if (opts?.packageOnly) {
|
|
133
|
+
const packageNames = (0, array_1.uniq)(names
|
|
134
|
+
.flatMap((n) => (0, strategy_normalize_1.normalizeStrategyUnits)(this.readStrategy(n)).map((u) => (0, parse_pinned_spec_1.parsePinnedSpec)(u.ref).name))
|
|
135
|
+
.filter((x) => x.trim().length > 0));
|
|
136
|
+
packageNames.forEach((pkg) => {
|
|
137
|
+
this.unitStore.updateToLatest(pkg);
|
|
138
|
+
});
|
|
139
|
+
const afterInstalled = this.unitStore.readInstalledDependencyVersions();
|
|
140
|
+
const afterStrategies = (0, strategy_config_1.listStrategyNamesFromConfig)(config).map((n) => ({
|
|
141
|
+
name: n,
|
|
142
|
+
units: (0, strategy_normalize_1.normalizeStrategyUnits)(this.readStrategy(n))
|
|
143
|
+
}));
|
|
144
|
+
const wanted = (0, strategy_analyze_1.collectWantedVersions)(afterStrategies);
|
|
145
|
+
const conflicts = (0, strategy_analyze_1.computeConflicts)(wanted);
|
|
146
|
+
if (conflicts.length > 0) {
|
|
147
|
+
throw new Error(`Strategy version conflicts detected (${conflicts.length}). Run 'pp strategy doctor' for details.`);
|
|
148
|
+
}
|
|
149
|
+
const drift = afterStrategies
|
|
150
|
+
.map((s) => (0, strategy_analyze_1.computeDrift)({ strategyName: s.name, units: s.units, installed: afterInstalled }))
|
|
151
|
+
.filter((d) => d.items.length > 0);
|
|
152
|
+
if (drift.length > 0) {
|
|
153
|
+
throw new Error("Strategy drift detected after --package-only update. Run 'pp strategy doctor'.");
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const nextStrategies = [];
|
|
158
|
+
const npmPacksToUpdate = new Set();
|
|
159
|
+
for (const n of names) {
|
|
160
|
+
const current = this.readStrategy(n);
|
|
161
|
+
const src = current.source;
|
|
162
|
+
if (!src) {
|
|
163
|
+
throw new Error(`Strategy ${n} has no source. Re-add it with a URL/path source to enable update.`);
|
|
164
|
+
}
|
|
165
|
+
if (src.type === "npm") {
|
|
166
|
+
npmPacksToUpdate.add(src.value);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const fetched = await (0, strategy_fetch_1.fetchStrategyFile)(src.value);
|
|
170
|
+
if (fetched.strategy.name !== n) {
|
|
171
|
+
throw new Error(`Strategy name mismatch while updating ${n}: got ${fetched.strategy.name}`);
|
|
172
|
+
}
|
|
173
|
+
const file = { ...fetched.strategy, source: fetched.source };
|
|
174
|
+
const units = (0, strategy_normalize_1.normalizeStrategyUnits)(file);
|
|
175
|
+
nextStrategies.push({ name: n, file, units });
|
|
176
|
+
}
|
|
177
|
+
if (npmPacksToUpdate.size > 0) {
|
|
178
|
+
const specs = [...npmPacksToUpdate];
|
|
179
|
+
specs.forEach((spec) => {
|
|
180
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(spec);
|
|
181
|
+
this.packStore.updateToLatest(pinned.name);
|
|
182
|
+
});
|
|
183
|
+
const installedPackVersions = this.packStore.readInstalledDependencyVersions();
|
|
184
|
+
const updatedSpecs = specs.map((spec) => {
|
|
185
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(spec);
|
|
186
|
+
const v = installedPackVersions[pinned.name];
|
|
187
|
+
if (!v) {
|
|
188
|
+
throw new Error(`Strategy pack not installed after update: ${pinned.name}`);
|
|
189
|
+
}
|
|
190
|
+
return `${pinned.name}@${v}`;
|
|
191
|
+
});
|
|
192
|
+
const updatedFiles = [];
|
|
193
|
+
updatedSpecs.forEach((spec) => {
|
|
194
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(spec);
|
|
195
|
+
const root = this.packStore.resolvePackRoot(pinned.name);
|
|
196
|
+
const files = (0, strategy_pack_read_1.readStrategiesFromPackRoot)(root).map((f) => ({
|
|
197
|
+
...f,
|
|
198
|
+
source: { type: "npm", value: spec }
|
|
199
|
+
}));
|
|
200
|
+
updatedFiles.push(...files);
|
|
201
|
+
});
|
|
202
|
+
updatedFiles.forEach((file) => {
|
|
203
|
+
const units = (0, strategy_normalize_1.normalizeStrategyUnits)(file);
|
|
204
|
+
nextStrategies.push({ name: file.name, file, units });
|
|
205
|
+
});
|
|
206
|
+
const nextConfig = this.configService.readConfigOrDefault();
|
|
207
|
+
const existing = nextConfig.strategyPacks ?? [];
|
|
208
|
+
const filtered = existing.filter((x) => {
|
|
209
|
+
if (typeof x === "string") {
|
|
210
|
+
return x.trim().length > 0;
|
|
211
|
+
}
|
|
212
|
+
return !updatedSpecs.some((spec) => (0, parse_pinned_spec_1.parsePinnedSpec)(spec).name === x.name);
|
|
213
|
+
});
|
|
214
|
+
const packsNext = [
|
|
215
|
+
...filtered,
|
|
216
|
+
...updatedSpecs.map((spec) => {
|
|
217
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(spec);
|
|
218
|
+
return { name: pinned.name, spec: pinned.spec };
|
|
219
|
+
})
|
|
220
|
+
];
|
|
221
|
+
this.configService.writeConfig({ ...nextConfig, strategyPacks: packsNext });
|
|
222
|
+
}
|
|
223
|
+
const allOtherNames = (0, strategy_config_1.listStrategyNamesFromConfig)(config).filter((n) => !names.includes(n));
|
|
224
|
+
const otherStrategies = allOtherNames.map((n) => ({
|
|
225
|
+
name: n,
|
|
226
|
+
units: (0, strategy_normalize_1.normalizeStrategyUnits)(this.readStrategy(n))
|
|
227
|
+
}));
|
|
228
|
+
const wanted = (0, strategy_analyze_1.collectWantedVersions)([
|
|
229
|
+
...otherStrategies,
|
|
230
|
+
...nextStrategies.map((s) => ({ name: s.name, units: s.units }))
|
|
231
|
+
]);
|
|
232
|
+
const conflicts = (0, strategy_analyze_1.computeConflicts)(wanted);
|
|
233
|
+
if (conflicts.length > 0) {
|
|
234
|
+
throw new Error(`Strategy version conflicts detected (${conflicts.length}). Run 'pp strategy doctor' for details.`);
|
|
235
|
+
}
|
|
236
|
+
const refs = (0, array_1.uniq)(nextStrategies.flatMap((s) => s.units.map((u) => u.ref)));
|
|
237
|
+
(0, strategy_analyze_1.ensureNoInstalledVersionConflicts)(installed, refs);
|
|
238
|
+
refs.forEach((ref) => {
|
|
239
|
+
this.unitStore.installPinned(ref);
|
|
240
|
+
});
|
|
241
|
+
nextStrategies.forEach((s) => {
|
|
242
|
+
(0, strategy_io_1.writeJsonAtomic)(this.getStrategyPath(s.name), s.file);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
pinStrategy(name) {
|
|
246
|
+
this.configService.ensureConfigFileExists();
|
|
247
|
+
const config = this.configService.readConfigOrDefault();
|
|
248
|
+
(0, strategy_config_1.requireStrategyInstalled)(config, name);
|
|
249
|
+
const file = this.readStrategy(name);
|
|
250
|
+
const units = (0, strategy_normalize_1.normalizeStrategyUnits)(file);
|
|
251
|
+
const installed = this.unitStore.readInstalledDependencyVersions();
|
|
252
|
+
const pinnedUnits = units.map((u) => {
|
|
253
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(u.ref);
|
|
254
|
+
const v = installed[pinned.name];
|
|
255
|
+
if (!v) {
|
|
256
|
+
throw new Error(`Unit package is not installed: ${pinned.name}`);
|
|
257
|
+
}
|
|
258
|
+
const nextRef = `${pinned.name}@${v}`;
|
|
259
|
+
return u.args.length === 0 ? nextRef : { ref: nextRef, args: u.args };
|
|
260
|
+
});
|
|
261
|
+
const others = (0, strategy_config_1.listStrategyNamesFromConfig)(config)
|
|
262
|
+
.filter((n) => n !== name)
|
|
263
|
+
.map((n) => ({ name: n, units: (0, strategy_normalize_1.normalizeStrategyUnits)(this.readStrategy(n)) }));
|
|
264
|
+
const nextWanted = (0, strategy_analyze_1.collectWantedVersions)([
|
|
265
|
+
...others,
|
|
266
|
+
{
|
|
267
|
+
name,
|
|
268
|
+
units: pinnedUnits.map((x) => typeof x === "string" ? { ref: x, args: [] } : { ref: x.ref, args: x.args ?? [] })
|
|
269
|
+
}
|
|
270
|
+
]);
|
|
271
|
+
const conflicts = (0, strategy_analyze_1.computeConflicts)(nextWanted);
|
|
272
|
+
if (conflicts.length > 0) {
|
|
273
|
+
throw new Error(`Strategy version conflicts detected (${conflicts.length}). Run 'pp strategy doctor' for details.`);
|
|
274
|
+
}
|
|
275
|
+
const nextFile = {
|
|
276
|
+
...file,
|
|
277
|
+
pipeline: {
|
|
278
|
+
...file.pipeline,
|
|
279
|
+
units: pinnedUnits
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
(0, strategy_io_1.writeJsonAtomic)(this.getStrategyPath(name), nextFile);
|
|
283
|
+
}
|
|
284
|
+
doctor() {
|
|
285
|
+
this.configService.ensureConfigFileExists();
|
|
286
|
+
const config = this.configService.readConfigOrDefault();
|
|
287
|
+
const names = (0, strategy_config_1.listStrategyNamesFromConfig)(config);
|
|
288
|
+
const installed = this.unitStore.readInstalledDependencyVersions();
|
|
289
|
+
const strategies = names.map((n) => ({
|
|
290
|
+
name: n,
|
|
291
|
+
units: (0, strategy_normalize_1.normalizeStrategyUnits)(this.readStrategy(n))
|
|
292
|
+
}));
|
|
293
|
+
const wanted = (0, strategy_analyze_1.collectWantedVersions)(strategies);
|
|
294
|
+
const conflicts = (0, strategy_analyze_1.computeConflicts)(wanted);
|
|
295
|
+
const drift = strategies
|
|
296
|
+
.map((s) => (0, strategy_analyze_1.computeDrift)({ strategyName: s.name, units: s.units, installed }))
|
|
297
|
+
.filter((d) => d.items.length > 0);
|
|
298
|
+
return { conflicts, drift };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
exports.StrategyService = StrategyService;
|