@pagepocket/cli 0.9.2 → 0.10.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 +79 -95
- package/dist/commands/strategy/add.js +34 -0
- package/dist/commands/strategy/doctor.js +34 -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/read-installed-package-version.js +28 -0
- package/dist/services/strategy/strategy-analyze.js +69 -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 +300 -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 +37 -0
- package/dist/services/user-packages/user-package-installer.js +47 -0
- package/dist/services/user-packages/user-package-store.js +126 -0
- package/package.json +11 -10
package/dist/commands/archive.js
CHANGED
|
@@ -4,83 +4,97 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const core_1 = require("@oclif/core");
|
|
7
|
-
const build_snapshot_unit_1 = require("@pagepocket/build-snapshot-unit");
|
|
8
|
-
const capture_http_cdp_unit_1 = require("@pagepocket/capture-http-cdp-unit");
|
|
9
|
-
const capture_http_lighterceptor_unit_1 = require("@pagepocket/capture-http-lighterceptor-unit");
|
|
10
|
-
const capture_http_puppeteer_unit_1 = require("@pagepocket/capture-http-puppeteer-unit");
|
|
11
7
|
const lib_1 = require("@pagepocket/lib");
|
|
12
|
-
const single_file_unit_1 = require("@pagepocket/single-file-unit");
|
|
13
|
-
const write_down_unit_1 = require("@pagepocket/write-down-unit");
|
|
14
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
15
9
|
const load_configured_plugins_1 = require("../services/load-configured-plugins");
|
|
16
|
-
const
|
|
10
|
+
const config_service_1 = require("../services/config-service");
|
|
11
|
+
const read_installed_package_version_1 = require("../services/strategy/read-installed-package-version");
|
|
12
|
+
const strategy_service_1 = require("../services/strategy/strategy-service");
|
|
13
|
+
const strategy_normalize_1 = require("../services/strategy/strategy-normalize");
|
|
14
|
+
const parse_pinned_spec_1 = require("../services/user-packages/parse-pinned-spec");
|
|
15
|
+
const unit_store_1 = require("../services/units/unit-store");
|
|
17
16
|
const with_spinner_1 = require("../utils/with-spinner");
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
const loadStrategyUnits = async (input) => {
|
|
18
|
+
const strategyService = new strategy_service_1.StrategyService(input.configService);
|
|
19
|
+
const unitStore = new unit_store_1.UnitStore(input.configService);
|
|
20
|
+
strategyService.ensureConfigFileExists();
|
|
21
|
+
const strategyFile = strategyService.readStrategy(input.strategyName);
|
|
22
|
+
const normalized = (0, strategy_normalize_1.normalizeStrategyUnits)(strategyFile);
|
|
23
|
+
const installed = unitStore.readInstalledDependencyVersions();
|
|
24
|
+
const drift = normalized.flatMap((u) => {
|
|
25
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(u.ref);
|
|
26
|
+
const installedVersion = installed[pinned.name];
|
|
27
|
+
if (!installedVersion || installedVersion !== pinned.version) {
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
name: pinned.name,
|
|
31
|
+
pinned: pinned.version,
|
|
32
|
+
installed: installedVersion
|
|
33
|
+
}
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
});
|
|
38
|
+
if (drift.length > 0) {
|
|
39
|
+
const details = drift
|
|
40
|
+
.map((d) => {
|
|
41
|
+
const installedText = typeof d.installed === "string" ? d.installed : "(not installed)";
|
|
42
|
+
return `${d.name}: pinned ${d.pinned}, installed ${installedText}`;
|
|
43
|
+
})
|
|
44
|
+
.join("; ");
|
|
45
|
+
throw new Error(`Strategy drift detected (${input.strategyName}). ${details}. ` +
|
|
46
|
+
`Fix: pp strategy update ${input.strategyName} OR pp strategy pin ${input.strategyName}`);
|
|
47
|
+
}
|
|
48
|
+
const loadedUnits = await Promise.all(normalized.map(async (u) => unitStore.instantiateFromRef(u.ref, u.args)));
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
const dup = loadedUnits.find((u) => {
|
|
51
|
+
if (seen.has(u.id)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
seen.add(u.id);
|
|
55
|
+
return false;
|
|
56
|
+
});
|
|
57
|
+
if (dup) {
|
|
58
|
+
throw new Error(`Duplicate unit id detected in strategy ${input.strategyName}: ${dup.id}`);
|
|
22
59
|
}
|
|
23
|
-
return
|
|
60
|
+
return loadedUnits;
|
|
24
61
|
};
|
|
25
62
|
class ArchiveCommand extends core_1.Command {
|
|
26
63
|
async run() {
|
|
27
64
|
const { args, flags } = await this.parse(ArchiveCommand);
|
|
28
65
|
const targetUrl = args.url;
|
|
29
|
-
const outputFlag = flags.output ? flags.output.trim() : undefined;
|
|
30
|
-
const emit = flags.emit;
|
|
31
|
-
const type = flags.type;
|
|
32
|
-
const overwrite = flags.overwrite === true;
|
|
33
|
-
const triggerActions = (flags.action ?? []);
|
|
34
|
-
const runtime = flags.runtime;
|
|
35
66
|
const timeoutMs = typeof flags.timeout === "number" ? flags.timeout : undefined;
|
|
36
67
|
const maxDurationMs = typeof flags.maxDuration === "number" ? flags.maxDuration : undefined;
|
|
37
|
-
const
|
|
38
|
-
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
|
39
|
-
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
40
|
-
"accept-language": "en-US,en;q=0.9",
|
|
41
|
-
referer: targetUrl
|
|
42
|
-
};
|
|
68
|
+
const strategyName = typeof flags.strategy === "string" ? flags.strategy.trim() : undefined;
|
|
43
69
|
const pagepocket = lib_1.PagePocket.fromURL(targetUrl);
|
|
44
|
-
if (type === "single-file" && emit === "zip") {
|
|
45
|
-
throw new Error("--type single-file is not compatible with --emit zip (single-file is a directory output)");
|
|
46
|
-
}
|
|
47
70
|
const result = await (0, with_spinner_1.withSpinner)(async (spinner) => {
|
|
48
71
|
{
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
:
|
|
59
|
-
|
|
60
|
-
new network_observer_unit_1.NetworkObserverUnit({
|
|
61
|
-
onRequest: (event) => {
|
|
62
|
-
spinner.text = `Freezing page (${formatStatusUrl(event.url)})`;
|
|
63
|
-
}
|
|
64
|
-
}),
|
|
65
|
-
captureUnit,
|
|
66
|
-
new build_snapshot_unit_1.BuildSnapshotUnit()
|
|
67
|
-
];
|
|
68
|
-
if (type === "single-file") {
|
|
69
|
-
units.push(new single_file_unit_1.SingleFileUnit());
|
|
72
|
+
const configService = new config_service_1.ConfigService();
|
|
73
|
+
const name = strategyName && strategyName.length > 0 ? strategyName : "default";
|
|
74
|
+
const strategyService = new strategy_service_1.StrategyService(configService);
|
|
75
|
+
strategyService.ensureConfigFileExists();
|
|
76
|
+
const installedNames = strategyService.listInstalledStrategyNames();
|
|
77
|
+
if (installedNames.length === 0) {
|
|
78
|
+
const v = (0, read_installed_package_version_1.readInstalledPackageVersion)("@pagepocket/builtin-strategy");
|
|
79
|
+
await strategyService.addStrategy({
|
|
80
|
+
source: `@pagepocket/builtin-strategy@${v}`,
|
|
81
|
+
force: false
|
|
82
|
+
});
|
|
70
83
|
}
|
|
71
|
-
units
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
: pagepocket;
|
|
84
|
+
const units = await loadStrategyUnits({
|
|
85
|
+
strategyName: name,
|
|
86
|
+
configService
|
|
87
|
+
});
|
|
88
|
+
const entryTarget = pagepocket;
|
|
89
|
+
const strategyFile = strategyService.readStrategy(name);
|
|
90
|
+
const captureOptions = strategyFile.pipeline.captureOptions;
|
|
91
|
+
const effectiveTimeoutMs = typeof timeoutMs === "number" ? timeoutMs : captureOptions?.timeoutMs;
|
|
92
|
+
const effectiveMaxDurationMs = typeof maxDurationMs === "number" ? maxDurationMs : captureOptions?.maxDurationMs;
|
|
81
93
|
const result = await entryTarget.capture({
|
|
82
|
-
timeoutMs,
|
|
83
|
-
|
|
94
|
+
...(typeof effectiveTimeoutMs === "number" ? { timeoutMs: effectiveTimeoutMs } : {}),
|
|
95
|
+
...(typeof effectiveMaxDurationMs === "number"
|
|
96
|
+
? { maxDurationMs: effectiveMaxDurationMs }
|
|
97
|
+
: {}),
|
|
84
98
|
blacklist: [...lib_1.ga, ...lib_1.ns],
|
|
85
99
|
units,
|
|
86
100
|
plugins: await (0, load_configured_plugins_1.loadConfiguredPlugins)().catch(() => [])
|
|
@@ -109,47 +123,17 @@ ArchiveCommand.flags = {
|
|
|
109
123
|
help: core_1.Flags.help({
|
|
110
124
|
char: "h"
|
|
111
125
|
}),
|
|
112
|
-
output: core_1.Flags.string({
|
|
113
|
-
char: "o",
|
|
114
|
-
description: "Output directory"
|
|
115
|
-
}),
|
|
116
|
-
emit: core_1.Flags.string({
|
|
117
|
-
description: "Output format",
|
|
118
|
-
options: ["raw", "zip"],
|
|
119
|
-
default: "raw"
|
|
120
|
-
}),
|
|
121
|
-
type: core_1.Flags.string({
|
|
122
|
-
description: "Snapshot type",
|
|
123
|
-
options: ["directory", "single-file"],
|
|
124
|
-
default: "directory"
|
|
125
|
-
}),
|
|
126
|
-
overwrite: core_1.Flags.boolean({
|
|
127
|
-
description: "Overwrite existing output directory instead of suffixing",
|
|
128
|
-
default: false
|
|
129
|
-
}),
|
|
130
|
-
action: core_1.Flags.string({
|
|
131
|
-
char: "a",
|
|
132
|
-
description: "Trigger action to run during/after capture (repeatable). Use -a multiple times to run multiple actions.",
|
|
133
|
-
options: Object.values({ HOVER: "HOVER", SCROLL_TO_END: "SCROLL_TO_END" }),
|
|
134
|
-
multiple: true
|
|
135
|
-
}),
|
|
136
|
-
runtime: core_1.Flags.string({
|
|
137
|
-
char: "r",
|
|
138
|
-
description: "Interceptor runtime to use",
|
|
139
|
-
options: ["lighterceptor", "puppeteer", "cdp"],
|
|
140
|
-
default: "puppeteer"
|
|
141
|
-
}),
|
|
142
|
-
tabId: core_1.Flags.integer({
|
|
143
|
-
description: "CDP tabId (required when --runtime cdp)"
|
|
144
|
-
}),
|
|
145
126
|
timeout: core_1.Flags.integer({
|
|
146
127
|
char: "t",
|
|
147
128
|
description: "Network idle duration in milliseconds before capture stops",
|
|
148
|
-
|
|
129
|
+
required: false
|
|
149
130
|
}),
|
|
150
131
|
maxDuration: core_1.Flags.integer({
|
|
151
132
|
description: "Hard max capture duration in milliseconds",
|
|
152
|
-
|
|
133
|
+
required: false
|
|
134
|
+
}),
|
|
135
|
+
strategy: core_1.Flags.string({
|
|
136
|
+
description: "Run an installed strategy (unit pipeline) by name"
|
|
153
137
|
})
|
|
154
138
|
};
|
|
155
139
|
exports.default = ArchiveCommand;
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const strategy_service_1 = require("../../services/strategy/strategy-service");
|
|
9
|
+
class StrategyAddCommand extends core_1.Command {
|
|
10
|
+
async run() {
|
|
11
|
+
const { args, flags } = await this.parse(StrategyAddCommand);
|
|
12
|
+
const service = new strategy_service_1.StrategyService();
|
|
13
|
+
service.ensureConfigFileExists();
|
|
14
|
+
const out = await service.addStrategy({
|
|
15
|
+
source: args.source,
|
|
16
|
+
force: flags.force === true
|
|
17
|
+
});
|
|
18
|
+
out.installedStrategies.forEach((name) => {
|
|
19
|
+
this.log(chalk_1.default.green(`Added strategy: ${name}`));
|
|
20
|
+
});
|
|
21
|
+
out.installedRefs.forEach((ref) => {
|
|
22
|
+
this.log(chalk_1.default.green(`Installed unit: ${ref}`));
|
|
23
|
+
});
|
|
24
|
+
void flags;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
StrategyAddCommand.description = "Install strategies from a file/url OR install a strategy pack from a pinned npm package (applies all *.strategy.json).";
|
|
28
|
+
StrategyAddCommand.args = {
|
|
29
|
+
source: core_1.Args.string({ description: "strategy file/url OR pinned npm package", required: true })
|
|
30
|
+
};
|
|
31
|
+
StrategyAddCommand.flags = {
|
|
32
|
+
force: core_1.Flags.boolean({ description: "overwrite existing strategy", default: false })
|
|
33
|
+
};
|
|
34
|
+
exports.default = StrategyAddCommand;
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const strategy_service_1 = require("../../services/strategy/strategy-service");
|
|
9
|
+
class StrategyDoctorCommand extends core_1.Command {
|
|
10
|
+
async run() {
|
|
11
|
+
const service = new strategy_service_1.StrategyService();
|
|
12
|
+
const report = service.doctor();
|
|
13
|
+
report.conflicts.forEach((c) => {
|
|
14
|
+
this.log(chalk_1.default.red(`[CONFLICT] ${c.name}`));
|
|
15
|
+
Object.entries(c.versions).forEach(([version, strategies]) => {
|
|
16
|
+
this.log(chalk_1.default.red(` ${version}: ${strategies.join(", ")}`));
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
report.drift.forEach((d) => {
|
|
20
|
+
this.log(chalk_1.default.yellow(`[DRIFT] ${d.strategy}`));
|
|
21
|
+
d.items.forEach((item) => {
|
|
22
|
+
const installed = typeof item.installed === "string" ? item.installed : "(not installed)";
|
|
23
|
+
this.log(chalk_1.default.yellow(` ${item.name}: pinned ${item.pinned}, installed ${installed}`));
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
if (report.conflicts.length === 0 && report.drift.length === 0) {
|
|
27
|
+
this.log(chalk_1.default.green("Strategy doctor checks passed."));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
throw new Error("Strategy doctor checks failed.");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
StrategyDoctorCommand.description = "Diagnose strategy conflicts and unit package drift.";
|
|
34
|
+
exports.default = StrategyDoctorCommand;
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const strategy_service_1 = require("../../services/strategy/strategy-service");
|
|
9
|
+
class StrategyPinCommand extends core_1.Command {
|
|
10
|
+
async run() {
|
|
11
|
+
const { args } = await this.parse(StrategyPinCommand);
|
|
12
|
+
const service = new strategy_service_1.StrategyService();
|
|
13
|
+
service.pinStrategy(args.name);
|
|
14
|
+
this.log(chalk_1.default.green(`Pinned strategy: ${args.name}`));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
StrategyPinCommand.description = "Pin a strategy file to currently installed unit package versions.";
|
|
18
|
+
StrategyPinCommand.args = {
|
|
19
|
+
name: core_1.Args.string({ description: "strategy name", required: true })
|
|
20
|
+
};
|
|
21
|
+
exports.default = StrategyPinCommand;
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const strategy_service_1 = require("../../services/strategy/strategy-service");
|
|
9
|
+
class StrategyRemoveCommand extends core_1.Command {
|
|
10
|
+
async run() {
|
|
11
|
+
const { args } = await this.parse(StrategyRemoveCommand);
|
|
12
|
+
const service = new strategy_service_1.StrategyService();
|
|
13
|
+
service.removeStrategy(args.name);
|
|
14
|
+
this.log(chalk_1.default.green(`Removed strategy: ${args.name}`));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
StrategyRemoveCommand.description = "Remove an installed strategy.";
|
|
18
|
+
StrategyRemoveCommand.args = {
|
|
19
|
+
name: core_1.Args.string({ description: "strategy name", required: true })
|
|
20
|
+
};
|
|
21
|
+
exports.default = StrategyRemoveCommand;
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const strategy_service_1 = require("../../services/strategy/strategy-service");
|
|
9
|
+
class StrategyUpdateCommand extends core_1.Command {
|
|
10
|
+
async run() {
|
|
11
|
+
const { args, flags } = await this.parse(StrategyUpdateCommand);
|
|
12
|
+
const service = new strategy_service_1.StrategyService();
|
|
13
|
+
await service.updateStrategy(args.name, { packageOnly: flags.packageOnly === true });
|
|
14
|
+
if (flags.packageOnly) {
|
|
15
|
+
this.log(chalk_1.default.yellow("WARNING: Updated unit packages without updating strategy files. This may cause version drift."));
|
|
16
|
+
this.log(chalk_1.default.gray("If drift occurs, fix by updating packages to pinned versions:"));
|
|
17
|
+
this.log(chalk_1.default.gray(` pp strategy update${args.name ? ` ${args.name}` : ""}`));
|
|
18
|
+
this.log(chalk_1.default.gray("Or pin a strategy to installed versions:"));
|
|
19
|
+
this.log(chalk_1.default.gray(` pp strategy pin ${args.name ?? "<strategy-name>"}`));
|
|
20
|
+
}
|
|
21
|
+
this.log(chalk_1.default.green("Strategy update completed."));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
StrategyUpdateCommand.description = "Update a strategy (or all strategies).";
|
|
25
|
+
StrategyUpdateCommand.args = {
|
|
26
|
+
name: core_1.Args.string({ description: "strategy name (optional)", required: false })
|
|
27
|
+
};
|
|
28
|
+
StrategyUpdateCommand.flags = {
|
|
29
|
+
packageOnly: core_1.Flags.boolean({
|
|
30
|
+
description: "update unit packages to latest only",
|
|
31
|
+
default: false
|
|
32
|
+
})
|
|
33
|
+
};
|
|
34
|
+
exports.default = StrategyUpdateCommand;
|
|
@@ -10,7 +10,9 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
10
10
|
const env_paths_1 = __importDefault(require("env-paths"));
|
|
11
11
|
const parse_json_1 = require("../utils/parse-json");
|
|
12
12
|
const defaultConfig = {
|
|
13
|
-
plugins: []
|
|
13
|
+
plugins: [],
|
|
14
|
+
strategies: [],
|
|
15
|
+
strategyPacks: []
|
|
14
16
|
};
|
|
15
17
|
class ConfigService {
|
|
16
18
|
constructor(appName = "pagepocket") {
|
|
@@ -57,27 +59,57 @@ class ConfigService {
|
|
|
57
59
|
return defaultConfig;
|
|
58
60
|
}
|
|
59
61
|
const pluginsRaw = value.plugins;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
62
|
+
const plugins = Array.isArray(pluginsRaw)
|
|
63
|
+
? pluginsRaw
|
|
64
|
+
.map((entry) => {
|
|
65
|
+
if (typeof entry === "string") {
|
|
66
|
+
return entry;
|
|
67
|
+
}
|
|
68
|
+
if (this.isObject(entry) && typeof entry.name === "string") {
|
|
69
|
+
const spec = "spec" in entry && typeof entry.spec === "string"
|
|
70
|
+
? entry.spec
|
|
71
|
+
: undefined;
|
|
72
|
+
const options = "options" in entry ? entry.options : undefined;
|
|
73
|
+
return {
|
|
74
|
+
name: entry.name,
|
|
75
|
+
...(typeof spec === "undefined" ? {} : { spec }),
|
|
76
|
+
...(typeof options === "undefined" ? {} : { options })
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
})
|
|
81
|
+
.filter((x) => typeof x !== "undefined")
|
|
82
|
+
: [];
|
|
83
|
+
const strategiesRaw = value.strategies;
|
|
84
|
+
const strategies = Array.isArray(strategiesRaw)
|
|
85
|
+
? strategiesRaw
|
|
86
|
+
.map((x) => (typeof x === "string" ? x.trim() : ""))
|
|
87
|
+
.filter((x) => x.length > 0)
|
|
88
|
+
: [];
|
|
89
|
+
const packsRaw = value.strategyPacks;
|
|
90
|
+
const strategyPacks = Array.isArray(packsRaw)
|
|
91
|
+
? packsRaw
|
|
92
|
+
.map((entry) => {
|
|
93
|
+
if (typeof entry === "string") {
|
|
94
|
+
return entry.trim();
|
|
95
|
+
}
|
|
96
|
+
if (this.isObject(entry) &&
|
|
97
|
+
typeof entry.name === "string" &&
|
|
98
|
+
typeof entry.spec === "string") {
|
|
99
|
+
return {
|
|
100
|
+
name: entry.name.trim(),
|
|
101
|
+
spec: entry.spec.trim()
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
})
|
|
106
|
+
.filter((x) => Boolean(x))
|
|
107
|
+
: [];
|
|
108
|
+
return {
|
|
109
|
+
plugins,
|
|
110
|
+
strategies,
|
|
111
|
+
strategyPacks
|
|
112
|
+
};
|
|
81
113
|
}
|
|
82
114
|
readConfig() {
|
|
83
115
|
const configPath = this.getConfigPath();
|
|
@@ -0,0 +1,28 @@
|
|
|
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.readInstalledPackageVersion = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const parse_json_1 = require("../../utils/parse-json");
|
|
9
|
+
const isRecord = (value) => {
|
|
10
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
11
|
+
};
|
|
12
|
+
const readInstalledPackageVersion = (packageName) => {
|
|
13
|
+
const req = globalThis.require;
|
|
14
|
+
if (!req) {
|
|
15
|
+
throw new Error("require is not available in this runtime");
|
|
16
|
+
}
|
|
17
|
+
const pkgJsonPath = req.resolve(`${packageName}/package.json`);
|
|
18
|
+
const text = node_fs_1.default.readFileSync(pkgJsonPath, "utf8");
|
|
19
|
+
const parsed = (0, parse_json_1.parseJson)(text);
|
|
20
|
+
if (!parsed.ok) {
|
|
21
|
+
throw parsed.error;
|
|
22
|
+
}
|
|
23
|
+
if (!isRecord(parsed.value) || typeof parsed.value.version !== "string") {
|
|
24
|
+
throw new Error(`Failed to read package version: ${packageName}`);
|
|
25
|
+
}
|
|
26
|
+
return parsed.value.version;
|
|
27
|
+
};
|
|
28
|
+
exports.readInstalledPackageVersion = readInstalledPackageVersion;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureNoInstalledVersionConflicts = exports.computeDrift = exports.computeConflicts = exports.collectWantedVersions = exports.uniq = void 0;
|
|
4
|
+
const parse_pinned_spec_1 = require("../user-packages/parse-pinned-spec");
|
|
5
|
+
const uniq = (xs) => [...new Set(xs)];
|
|
6
|
+
exports.uniq = uniq;
|
|
7
|
+
const collectWantedVersions = (strategies) => {
|
|
8
|
+
var _a, _b, _c;
|
|
9
|
+
const wanted = {};
|
|
10
|
+
for (const s of strategies) {
|
|
11
|
+
for (const u of s.units) {
|
|
12
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(u.ref);
|
|
13
|
+
wanted[_a = pinned.name] ?? (wanted[_a] = {});
|
|
14
|
+
(_b = wanted[pinned.name])[_c = pinned.version] ?? (_b[_c] = []);
|
|
15
|
+
wanted[pinned.name][pinned.version].push(s.name);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return wanted;
|
|
19
|
+
};
|
|
20
|
+
exports.collectWantedVersions = collectWantedVersions;
|
|
21
|
+
const computeConflicts = (wanted) => {
|
|
22
|
+
const conflicts = [];
|
|
23
|
+
for (const [name, versions] of Object.entries(wanted)) {
|
|
24
|
+
const uniqueVersions = Object.keys(versions);
|
|
25
|
+
if (uniqueVersions.length <= 1) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
conflicts.push({ name, versions });
|
|
29
|
+
}
|
|
30
|
+
return conflicts;
|
|
31
|
+
};
|
|
32
|
+
exports.computeConflicts = computeConflicts;
|
|
33
|
+
const computeDrift = (input) => {
|
|
34
|
+
const items = input.units.flatMap((u) => {
|
|
35
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(u.ref);
|
|
36
|
+
const installedVersion = input.installed[pinned.name];
|
|
37
|
+
if (!installedVersion || installedVersion !== pinned.version) {
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
name: pinned.name,
|
|
41
|
+
pinned: pinned.version,
|
|
42
|
+
installed: installedVersion
|
|
43
|
+
}
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
return [];
|
|
47
|
+
});
|
|
48
|
+
return { strategy: input.strategyName, items };
|
|
49
|
+
};
|
|
50
|
+
exports.computeDrift = computeDrift;
|
|
51
|
+
const ensureNoInstalledVersionConflicts = (installed, refs) => {
|
|
52
|
+
const byName = {};
|
|
53
|
+
for (const ref of refs) {
|
|
54
|
+
const pinned = (0, parse_pinned_spec_1.parsePinnedSpec)(ref);
|
|
55
|
+
const already = byName[pinned.name];
|
|
56
|
+
if (already && already !== pinned.version) {
|
|
57
|
+
throw new Error(`Strategy contains conflicting versions for ${pinned.name}: ${already} vs ${pinned.version}`);
|
|
58
|
+
}
|
|
59
|
+
byName[pinned.name] = pinned.version;
|
|
60
|
+
}
|
|
61
|
+
for (const [name, version] of Object.entries(byName)) {
|
|
62
|
+
const installedVersion = installed[name];
|
|
63
|
+
if (installedVersion && installedVersion !== version) {
|
|
64
|
+
throw new Error(`Version conflict for ${name}: installed ${installedVersion}, strategy requires ${version}. ` +
|
|
65
|
+
"This CLI stores only one version per package name.");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
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 uniq = (xs) => [...new Set(xs)];
|
|
5
|
+
const listStrategyNamesFromConfig = (config) => {
|
|
6
|
+
return 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 = 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;
|