@askrjs/cli 0.0.5 → 0.0.6
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/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# @askrjs/cli
|
|
2
2
|
|
|
3
|
+
[](https://github.com/askrjs/askr-cli/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@askrjs/cli)
|
|
5
|
+
|
|
3
6
|
Unified CLI for the Askr platform.
|
|
4
7
|
|
|
5
8
|
`@askrjs/cli` bundles project scaffolding and static-site generation commands
|
|
@@ -117,15 +120,17 @@ and failed decisions without running an install.
|
|
|
117
120
|
askr outdated
|
|
118
121
|
askr update
|
|
119
122
|
askr upgrade
|
|
123
|
+
askr upgrade --force
|
|
120
124
|
askr update vite "@types/*"
|
|
121
125
|
askr update --workspace "@scope/app" --tag next --json
|
|
122
126
|
```
|
|
123
127
|
|
|
124
|
-
`askr update` writes safe range changes. `askr upgrade`
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
`askr update` writes safe range changes. `askr upgrade` jointly selects the newest
|
|
129
|
+
peer-compatible published versions up to each package's configured dist-tag,
|
|
130
|
+
including breaking changes. Required peers must be present; optional peers may
|
|
131
|
+
be absent. `askr upgrade --force` writes the dist-tag targets directly and skips
|
|
132
|
+
peer checks. Positional selection is strict: unselected dependencies constrain
|
|
133
|
+
the solution but are never rewritten.
|
|
129
134
|
|
|
130
135
|
The updater preserves exact, caret, tilde, and x-range styles. It can widen one
|
|
131
136
|
bounded interval, and it updates only the highest clause of a simple OR union.
|
|
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { load } from "js-yaml";
|
|
6
6
|
import { minimatch } from "minimatch";
|
|
7
|
+
import semver from "semver";
|
|
7
8
|
//#region src/update/types.ts
|
|
8
9
|
const DEPENDENCY_SECTIONS = [
|
|
9
10
|
"dependencies",
|
|
@@ -307,6 +308,7 @@ async function discoverProject(options) {
|
|
|
307
308
|
const selectedWorkspaces = options.workspacePatterns.length === 0 ? workspaces : workspaces.filter((workspace) => matchesAny(workspace.name, options.workspacePatterns));
|
|
308
309
|
if (selectedWorkspaces.length === 0) throw new Error("No discovered workspace matches the requested --workspace filters.");
|
|
309
310
|
const localNames = new Set(workspaces.map((workspace) => workspace.name));
|
|
311
|
+
const localVersions = new Map(workspaces.flatMap((workspace) => typeof workspace.manifest.version === "string" && semver.valid(workspace.manifest.version) ? [[workspace.name, workspace.manifest.version]] : []));
|
|
310
312
|
return {
|
|
311
313
|
root,
|
|
312
314
|
workspaces,
|
|
@@ -316,7 +318,8 @@ async function discoverProject(options) {
|
|
|
316
318
|
contextOccurrences: collectOccurrences(root, selectedWorkspaces, localNames, {
|
|
317
319
|
ignore: [],
|
|
318
320
|
tags: policy.tags
|
|
319
|
-
}, [])
|
|
321
|
+
}, []),
|
|
322
|
+
localVersions
|
|
320
323
|
};
|
|
321
324
|
}
|
|
322
325
|
//#endregion
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { n as isBreakingChange, r as rewriteRange, t as analyzeRange } from "./range-YUs9eimn.js";
|
|
2
|
+
import { t as parseDependencySpecification } from "./specification-DXnDOC-0.js";
|
|
3
|
+
import semver from "semver";
|
|
4
|
+
//#region src/update/planner.ts
|
|
5
|
+
const STATUS_PRIORITY = {
|
|
6
|
+
current: 0,
|
|
7
|
+
local: 1,
|
|
8
|
+
safe: 2,
|
|
9
|
+
breaking: 3,
|
|
10
|
+
manual: 4,
|
|
11
|
+
error: 5
|
|
12
|
+
};
|
|
13
|
+
const publishedVersions = (packument) => Object.keys(packument.versions ?? {}).filter((version) => semver.valid(version) !== null).sort(semver.compare);
|
|
14
|
+
function selectedTarget(packument, tag) {
|
|
15
|
+
const value = packument["dist-tags"]?.[tag];
|
|
16
|
+
return typeof value === "string" && semver.valid(value) ? value : null;
|
|
17
|
+
}
|
|
18
|
+
function resolveSpecificationVersion(specification, packument) {
|
|
19
|
+
const parsed = parseDependencySpecification(specification);
|
|
20
|
+
if (parsed.type === "tag") {
|
|
21
|
+
const value = packument["dist-tags"]?.[parsed.rawSpec];
|
|
22
|
+
return typeof value === "string" && semver.valid(value) ? value : null;
|
|
23
|
+
}
|
|
24
|
+
if (parsed.type === "version") return semver.valid(parsed.rawSpec);
|
|
25
|
+
if (parsed.type === "range") return semver.maxSatisfying(publishedVersions(packument), parsed.rawSpec);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function metadata(packument, version) {
|
|
29
|
+
const value = packument.versions?.[version];
|
|
30
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
31
|
+
}
|
|
32
|
+
function baseOccurrence(occurrence) {
|
|
33
|
+
return {
|
|
34
|
+
workspace: occurrence.workspace,
|
|
35
|
+
manifestPath: occurrence.manifestPath,
|
|
36
|
+
relativeManifestPath: occurrence.relativeManifestPath,
|
|
37
|
+
section: occurrence.section,
|
|
38
|
+
currentSpecification: occurrence.currentSpecification,
|
|
39
|
+
proposedSpecification: null,
|
|
40
|
+
allowedVersion: null,
|
|
41
|
+
selectedVersion: null
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function planOne(occurrence, packument, failure, tag, mode, chosen, blocker) {
|
|
45
|
+
const base = baseOccurrence(occurrence);
|
|
46
|
+
if (occurrence.kind === "local") return {
|
|
47
|
+
targetVersion: null,
|
|
48
|
+
occurrence: {
|
|
49
|
+
...base,
|
|
50
|
+
status: "local",
|
|
51
|
+
reason: occurrence.reason
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
if (occurrence.kind === "manual") return {
|
|
55
|
+
targetVersion: null,
|
|
56
|
+
occurrence: {
|
|
57
|
+
...base,
|
|
58
|
+
status: "manual",
|
|
59
|
+
reason: occurrence.reason
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
if (failure) return {
|
|
63
|
+
targetVersion: null,
|
|
64
|
+
occurrence: {
|
|
65
|
+
...base,
|
|
66
|
+
status: "error",
|
|
67
|
+
reason: failure
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
if (!packument) return {
|
|
71
|
+
targetVersion: null,
|
|
72
|
+
occurrence: {
|
|
73
|
+
...base,
|
|
74
|
+
status: "error",
|
|
75
|
+
reason: "package metadata is unavailable"
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const target = selectedTarget(packument, tag);
|
|
79
|
+
if (!target) return {
|
|
80
|
+
targetVersion: null,
|
|
81
|
+
occurrence: {
|
|
82
|
+
...base,
|
|
83
|
+
status: "error",
|
|
84
|
+
reason: `dist-tag '${tag}' is not published`
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
if (!publishedVersions(packument).includes(target)) return {
|
|
88
|
+
targetVersion: null,
|
|
89
|
+
occurrence: {
|
|
90
|
+
...base,
|
|
91
|
+
status: "error",
|
|
92
|
+
reason: `dist-tag '${tag}' does not identify a published version`
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
if (occurrence.kind === "current") return {
|
|
96
|
+
targetVersion: target,
|
|
97
|
+
occurrence: {
|
|
98
|
+
...base,
|
|
99
|
+
status: "current",
|
|
100
|
+
reason: occurrence.reason
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const allowed = resolveSpecificationVersion(occurrence.currentSpecification, packument);
|
|
104
|
+
if (!allowed) return {
|
|
105
|
+
targetVersion: target,
|
|
106
|
+
occurrence: {
|
|
107
|
+
...base,
|
|
108
|
+
status: "manual",
|
|
109
|
+
reason: "no published version satisfies the current specification"
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const selected = chosen ?? target;
|
|
113
|
+
const withVersions = {
|
|
114
|
+
...base,
|
|
115
|
+
allowedVersion: allowed,
|
|
116
|
+
selectedVersion: selected
|
|
117
|
+
};
|
|
118
|
+
if (blocker) return {
|
|
119
|
+
targetVersion: target,
|
|
120
|
+
occurrence: {
|
|
121
|
+
...withVersions,
|
|
122
|
+
status: "manual",
|
|
123
|
+
reason: blocker
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
if (semver.satisfies(selected, occurrence.currentSpecification) || !semver.gt(selected, allowed)) return {
|
|
127
|
+
targetVersion: target,
|
|
128
|
+
occurrence: {
|
|
129
|
+
...withVersions,
|
|
130
|
+
status: "current",
|
|
131
|
+
reason: blocker ?? "selected version is already covered by the current specification"
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const analysis = analyzeRange(occurrence.currentSpecification);
|
|
135
|
+
if (!analysis.shape) return {
|
|
136
|
+
targetVersion: target,
|
|
137
|
+
occurrence: {
|
|
138
|
+
...withVersions,
|
|
139
|
+
status: "manual",
|
|
140
|
+
reason: analysis.reason
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const breaking = isBreakingChange(allowed, selected);
|
|
144
|
+
if (mode === "update" && breaking) return {
|
|
145
|
+
targetVersion: target,
|
|
146
|
+
occurrence: {
|
|
147
|
+
...withVersions,
|
|
148
|
+
status: "breaking",
|
|
149
|
+
reason: "breaking update is available via askr upgrade"
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
targetVersion: target,
|
|
154
|
+
occurrence: {
|
|
155
|
+
...withVersions,
|
|
156
|
+
proposedSpecification: rewriteRange(analysis.shape, selected, breaking),
|
|
157
|
+
status: breaking ? "breaking" : "safe",
|
|
158
|
+
reason: selected === target ? "selected tag target is eligible" : `compatible version ${selected} selected below ${tag}@${target}`
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function optionalPeer(meta, peer) {
|
|
163
|
+
const value = meta.peerDependenciesMeta?.[peer];
|
|
164
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && value.optional === true);
|
|
165
|
+
}
|
|
166
|
+
function solveWorkspace(workspace, selectedOccurrences, context, packuments, tags, cliTag, localVersions, mode) {
|
|
167
|
+
const selectedNames = new Set(selectedOccurrences.filter((entry) => entry.kind === "fetch").map((entry) => entry.package));
|
|
168
|
+
const byName = /* @__PURE__ */ new Map();
|
|
169
|
+
for (const item of context.filter((entry) => entry.workspace === workspace)) if (!byName.has(item.package)) byName.set(item.package, item);
|
|
170
|
+
const states = /* @__PURE__ */ new Map();
|
|
171
|
+
for (const [name, occurrence] of byName) {
|
|
172
|
+
const packument = packuments.get(name);
|
|
173
|
+
const current = packument ? resolveSpecificationVersion(occurrence.currentSpecification, packument) : localVersions.get(name) ?? null;
|
|
174
|
+
let candidates = current ? [current] : [];
|
|
175
|
+
if (selectedNames.has(name) && packument && current) {
|
|
176
|
+
const target = selectedTarget(packument, cliTag ?? tags[name] ?? "latest");
|
|
177
|
+
if (target) candidates = publishedVersions(packument).filter((version) => semver.gte(version, current) && semver.lte(version, target) && (mode === "upgrade" || !isBreakingChange(current, version))).sort(semver.rcompare);
|
|
178
|
+
}
|
|
179
|
+
states.set(name, {
|
|
180
|
+
occurrence,
|
|
181
|
+
selected: selectedNames.has(name),
|
|
182
|
+
current,
|
|
183
|
+
candidates
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
const variables = [...states.entries()].filter(([, state]) => state.selected && state.candidates.length > 0).sort(([a], [b]) => a.localeCompare(b));
|
|
187
|
+
const fixed = new Map([...states].flatMap(([name, state]) => state.selected ? [] : state.current ? [[name, state.current]] : []));
|
|
188
|
+
let best = null;
|
|
189
|
+
let bestChanged = -1;
|
|
190
|
+
let firstFailure = "no jointly peer-compatible published version set exists";
|
|
191
|
+
const validate = (choices) => {
|
|
192
|
+
const installed = new Map([...fixed, ...choices]);
|
|
193
|
+
for (const [name, version] of installed) {
|
|
194
|
+
const packument = packuments.get(name);
|
|
195
|
+
if (!packument) continue;
|
|
196
|
+
const meta = metadata(packument, version);
|
|
197
|
+
if (!meta) continue;
|
|
198
|
+
for (const [peer, requirement] of Object.entries(meta.peerDependencies ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
|
|
199
|
+
if (typeof requirement !== "string") continue;
|
|
200
|
+
const providerChanged = states.get(name)?.current !== version;
|
|
201
|
+
const peerChanged = states.get(peer)?.selected && states.get(peer)?.current !== installed.get(peer);
|
|
202
|
+
if (!providerChanged && !peerChanged) continue;
|
|
203
|
+
const peerVersion = installed.get(peer) ?? localVersions.get(peer);
|
|
204
|
+
if (!peerVersion) {
|
|
205
|
+
if (optionalPeer(meta, peer)) continue;
|
|
206
|
+
return `${name}@${version} requires missing peer ${peer}@${requirement}`;
|
|
207
|
+
}
|
|
208
|
+
if (!semver.satisfies(peerVersion, requirement, { includePrerelease: true })) return `${name}@${version} requires ${peer}@${requirement}`;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
};
|
|
213
|
+
const visit = (index, choices) => {
|
|
214
|
+
if (index === variables.length) {
|
|
215
|
+
const failure = validate(choices);
|
|
216
|
+
if (failure) {
|
|
217
|
+
firstFailure = failure;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const changed = variables.filter(([name, state]) => choices.get(name) !== state.current).length;
|
|
221
|
+
if (changed > bestChanged) {
|
|
222
|
+
best = new Map(choices);
|
|
223
|
+
bestChanged = changed;
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const [name, state] = variables[index];
|
|
228
|
+
for (const version of state.candidates) {
|
|
229
|
+
choices.set(name, version);
|
|
230
|
+
visit(index + 1, choices);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
visit(0, /* @__PURE__ */ new Map());
|
|
234
|
+
const blockers = /* @__PURE__ */ new Map();
|
|
235
|
+
if (!best) for (const [name] of variables) blockers.set(name, firstFailure);
|
|
236
|
+
const choices = best ?? new Map(variables.flatMap(([name, state]) => state.current ? [[name, state.current]] : []));
|
|
237
|
+
for (const [name, state] of variables) {
|
|
238
|
+
if (choices.get(name) !== state.current || state.candidates[0] === state.current) continue;
|
|
239
|
+
const attempted = new Map(choices).set(name, state.candidates[0]);
|
|
240
|
+
blockers.set(name, validate(attempted) ?? "no jointly peer-compatible update advances this dependency");
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
choices,
|
|
244
|
+
blockers
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function aggregateStatus(occurrences) {
|
|
248
|
+
return occurrences.reduce((status, occurrence) => STATUS_PRIORITY[occurrence.status] > STATUS_PRIORITY[status] ? occurrence.status : status, "current");
|
|
249
|
+
}
|
|
250
|
+
function summarize(decisions) {
|
|
251
|
+
const summary = {
|
|
252
|
+
packages: decisions.length,
|
|
253
|
+
occurrences: 0,
|
|
254
|
+
changedOccurrences: 0,
|
|
255
|
+
current: 0,
|
|
256
|
+
safe: 0,
|
|
257
|
+
breaking: 0,
|
|
258
|
+
local: 0,
|
|
259
|
+
manual: 0,
|
|
260
|
+
error: 0
|
|
261
|
+
};
|
|
262
|
+
for (const decision of decisions) {
|
|
263
|
+
decision.status = aggregateStatus(decision.occurrences);
|
|
264
|
+
decision.reason = decision.occurrences.find((entry) => entry.status === decision.status)?.reason ?? "";
|
|
265
|
+
summary[decision.status] += 1;
|
|
266
|
+
summary.occurrences += decision.occurrences.length;
|
|
267
|
+
summary.changedOccurrences += decision.occurrences.filter((entry) => entry.proposedSpecification).length;
|
|
268
|
+
}
|
|
269
|
+
return summary;
|
|
270
|
+
}
|
|
271
|
+
function planUpdates(options) {
|
|
272
|
+
const failures = options.failures ?? /* @__PURE__ */ new Map();
|
|
273
|
+
const tags = options.tags ?? {};
|
|
274
|
+
const mode = options.mode ?? (options.force ? "upgrade" : "update");
|
|
275
|
+
const context = options.contextOccurrences ?? options.occurrences;
|
|
276
|
+
const workspaceSolutions = /* @__PURE__ */ new Map();
|
|
277
|
+
if (mode !== "force") for (const workspace of new Set(options.occurrences.map((entry) => entry.workspace))) workspaceSolutions.set(workspace, solveWorkspace(workspace, options.occurrences.filter((entry) => entry.workspace === workspace), context, options.packuments, tags, options.cliTag, options.localVersions ?? /* @__PURE__ */ new Map(), mode));
|
|
278
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
279
|
+
for (const occurrence of options.occurrences) grouped.set(occurrence.package, [...grouped.get(occurrence.package) ?? [], occurrence]);
|
|
280
|
+
const decisions = [...grouped.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([packageName, occurrences]) => {
|
|
281
|
+
const selectedTag = options.cliTag ?? tags[packageName] ?? "latest";
|
|
282
|
+
const planned = occurrences.map((occurrence) => {
|
|
283
|
+
const solution = workspaceSolutions.get(occurrence.workspace);
|
|
284
|
+
const blocker = solution?.blockers.get(packageName);
|
|
285
|
+
const chosen = mode === "update" && !blocker ? void 0 : solution?.choices.get(packageName);
|
|
286
|
+
return planOne(occurrence, options.packuments.get(packageName), failures.get(packageName), selectedTag, mode, chosen, blocker);
|
|
287
|
+
});
|
|
288
|
+
const targetVersion = planned.find((entry) => entry.targetVersion)?.targetVersion ?? null;
|
|
289
|
+
const plannedOccurrences = planned.map((entry) => entry.occurrence);
|
|
290
|
+
const status = aggregateStatus(plannedOccurrences);
|
|
291
|
+
return {
|
|
292
|
+
package: packageName,
|
|
293
|
+
selectedTag,
|
|
294
|
+
targetVersion,
|
|
295
|
+
status,
|
|
296
|
+
reason: plannedOccurrences.find((entry) => entry.status === status)?.reason ?? "",
|
|
297
|
+
occurrences: plannedOccurrences
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
const selectedNames = new Set(grouped.keys());
|
|
301
|
+
for (const [packageName, failure] of [...failures].sort(([a], [b]) => a.localeCompare(b))) {
|
|
302
|
+
if (selectedNames.has(packageName)) continue;
|
|
303
|
+
const occurrences = context.filter((entry) => entry.package === packageName).map((entry) => ({
|
|
304
|
+
...baseOccurrence(entry),
|
|
305
|
+
status: "error",
|
|
306
|
+
reason: `peer compatibility lookup failed: ${failure}`
|
|
307
|
+
}));
|
|
308
|
+
decisions.push({
|
|
309
|
+
package: packageName,
|
|
310
|
+
selectedTag: options.cliTag ?? tags[packageName] ?? "latest",
|
|
311
|
+
targetVersion: null,
|
|
312
|
+
status: "error",
|
|
313
|
+
reason: `peer compatibility lookup failed: ${failure}`,
|
|
314
|
+
occurrences
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
decisions.sort((a, b) => a.package.localeCompare(b.package));
|
|
318
|
+
const summary = summarize(decisions);
|
|
319
|
+
return {
|
|
320
|
+
decisions,
|
|
321
|
+
summary,
|
|
322
|
+
hasErrors: summary.error > 0
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
//#endregion
|
|
326
|
+
export { planUpdates };
|
|
@@ -130,6 +130,23 @@ function selectedVersions(packument, specifications) {
|
|
|
130
130
|
}
|
|
131
131
|
return [...selected].sort(semver.compare);
|
|
132
132
|
}
|
|
133
|
+
function candidateVersions(packument, specifications) {
|
|
134
|
+
const versions = Object.keys(packument.versions ?? {}).filter((version) => semver.valid(version) !== null);
|
|
135
|
+
const selected = selectedVersions(packument, specifications);
|
|
136
|
+
const lowerBounds = specifications.flatMap((specification) => {
|
|
137
|
+
const parsed = parseDependencySpecification(specification);
|
|
138
|
+
if (parsed.type === "version") return semver.valid(parsed.rawSpec) ? [parsed.rawSpec] : [];
|
|
139
|
+
if (parsed.type === "range") {
|
|
140
|
+
const current = semver.maxSatisfying(versions, parsed.rawSpec);
|
|
141
|
+
return current ? [current] : [];
|
|
142
|
+
}
|
|
143
|
+
return [];
|
|
144
|
+
});
|
|
145
|
+
if (lowerBounds.length === 0 || selected.length === 0) return selected;
|
|
146
|
+
const floor = lowerBounds.sort(semver.compare)[0];
|
|
147
|
+
const ceiling = selected.sort(semver.compare)[selected.length - 1];
|
|
148
|
+
return versions.filter((version) => semver.gte(version, floor) && semver.lte(version, ceiling));
|
|
149
|
+
}
|
|
133
150
|
function mergeVersionMetadata(packument, value, selected) {
|
|
134
151
|
const entries = Array.isArray(value) ? value : [value];
|
|
135
152
|
const merged = /* @__PURE__ */ new Set();
|
|
@@ -142,6 +159,7 @@ function mergeVersionMetadata(packument, value, selected) {
|
|
|
142
159
|
const metadata = packument.versions?.[entry.version];
|
|
143
160
|
if (!isRecord(metadata)) continue;
|
|
144
161
|
if (isRecord(entry.peerDependencies)) metadata.peerDependencies = entry.peerDependencies;
|
|
162
|
+
if (isRecord(entry.peerDependenciesMeta)) metadata.peerDependenciesMeta = entry.peerDependenciesMeta;
|
|
145
163
|
merged.add(entry.version);
|
|
146
164
|
}
|
|
147
165
|
return selected.every((version) => merged.has(version));
|
|
@@ -154,13 +172,14 @@ async function executeNpmView(packageName, configuration, specifications) {
|
|
|
154
172
|
"dist-tags"
|
|
155
173
|
], configuration, true));
|
|
156
174
|
if (!packument) return null;
|
|
157
|
-
const versions =
|
|
175
|
+
const versions = candidateVersions(packument, specifications);
|
|
158
176
|
if (versions.length === 0) return packument;
|
|
159
177
|
return mergeVersionMetadata(packument, await executeNpmJson([
|
|
160
178
|
"view",
|
|
161
179
|
`${packageName}@${versions.join(" || ")}`,
|
|
162
180
|
"version",
|
|
163
|
-
"peerDependencies"
|
|
181
|
+
"peerDependencies",
|
|
182
|
+
"peerDependenciesMeta"
|
|
164
183
|
], configuration, false), versions) ? packument : null;
|
|
165
184
|
}
|
|
166
185
|
function isPackument(value) {
|
package/dist/update.js
CHANGED
|
@@ -14,6 +14,7 @@ function helpText(command) {
|
|
|
14
14
|
" --workspace <glob> Select workspace names (repeatable)",
|
|
15
15
|
" --tag <tag> Use one registry dist-tag for selected packages",
|
|
16
16
|
" --json Emit one deterministic JSON object on stdout",
|
|
17
|
+
...command === "upgrade" ? [" --force, -f Use tag targets without peer compatibility checks"] : [],
|
|
17
18
|
" --help, -h Show this help message",
|
|
18
19
|
"",
|
|
19
20
|
"Notes:",
|
|
@@ -29,11 +30,12 @@ function optionValue(args, index, option, errors) {
|
|
|
29
30
|
}
|
|
30
31
|
return args[index + 1];
|
|
31
32
|
}
|
|
32
|
-
function parseArgs(args) {
|
|
33
|
+
function parseArgs(command, args) {
|
|
33
34
|
const parsed = {
|
|
34
35
|
cwd: process.cwd(),
|
|
35
36
|
help: false,
|
|
36
37
|
json: false,
|
|
38
|
+
force: false,
|
|
37
39
|
packagePatterns: [],
|
|
38
40
|
workspacePatterns: [],
|
|
39
41
|
errors: []
|
|
@@ -45,6 +47,8 @@ function parseArgs(args) {
|
|
|
45
47
|
else if (argument === "--") positionalOnly = true;
|
|
46
48
|
else if (argument === "--help" || argument === "-h") parsed.help = true;
|
|
47
49
|
else if (argument === "--json") parsed.json = true;
|
|
50
|
+
else if (argument === "--force" || argument === "-f") if (command === "upgrade") parsed.force = true;
|
|
51
|
+
else parsed.errors.push(`${argument} is only supported by askr upgrade`);
|
|
48
52
|
else if (argument === "--cwd") {
|
|
49
53
|
const value = optionValue(args, index, "--cwd", parsed.errors);
|
|
50
54
|
if (value !== null) {
|
|
@@ -101,6 +105,7 @@ function serializableDecision(decision) {
|
|
|
101
105
|
currentSpecification: occurrence.currentSpecification,
|
|
102
106
|
proposedSpecification: occurrence.proposedSpecification,
|
|
103
107
|
allowedVersion: occurrence.allowedVersion,
|
|
108
|
+
selectedVersion: occurrence.selectedVersion,
|
|
104
109
|
status: occurrence.status,
|
|
105
110
|
reason: occurrence.reason
|
|
106
111
|
}))
|
|
@@ -132,6 +137,7 @@ function renderTable(rows) {
|
|
|
132
137
|
const headings = [
|
|
133
138
|
"Package",
|
|
134
139
|
"Allowed",
|
|
140
|
+
"Chosen",
|
|
135
141
|
"Latest",
|
|
136
142
|
"Status",
|
|
137
143
|
"Range"
|
|
@@ -147,6 +153,7 @@ function emitHuman(io, decisions, summary, applied, command) {
|
|
|
147
153
|
const row = [
|
|
148
154
|
decision.package,
|
|
149
155
|
occurrence.allowedVersion ?? "-",
|
|
156
|
+
occurrence.selectedVersion ?? "-",
|
|
150
157
|
decision.targetVersion ?? "-",
|
|
151
158
|
occurrence.status,
|
|
152
159
|
rangeText(occurrence, command)
|
|
@@ -186,11 +193,11 @@ function collectEdits(decisions) {
|
|
|
186
193
|
}] : []));
|
|
187
194
|
}
|
|
188
195
|
async function defaultRegistry(root, packageNames, requirements) {
|
|
189
|
-
const { fetchPackuments, loadNpmConfiguration } = await import("./registry-
|
|
196
|
+
const { fetchPackuments, loadNpmConfiguration } = await import("./registry-D2APi6x0.js");
|
|
190
197
|
return fetchPackuments(packageNames, await loadNpmConfiguration(root), { requirements });
|
|
191
198
|
}
|
|
192
199
|
async function runDependencyCli(command, args, io = console, runtime = {}) {
|
|
193
|
-
const parsed = parseArgs(args);
|
|
200
|
+
const parsed = parseArgs(command, args);
|
|
194
201
|
if (parsed.help && parsed.errors.length === 0) {
|
|
195
202
|
io.log(helpText(command));
|
|
196
203
|
return 0;
|
|
@@ -210,7 +217,7 @@ async function runDependencyCli(command, args, io = console, runtime = {}) {
|
|
|
210
217
|
let root = null;
|
|
211
218
|
let selectedWorkspaces = [];
|
|
212
219
|
try {
|
|
213
|
-
const [{ discoverProject }, { planUpdates }] = await Promise.all([import("./discovery-
|
|
220
|
+
const [{ discoverProject }, { planUpdates }] = await Promise.all([import("./discovery-Djq8TxJu.js"), import("./planner-ZAX9SFjY.js")]);
|
|
214
221
|
const project = await discoverProject({
|
|
215
222
|
cwd: parsed.cwd,
|
|
216
223
|
packagePatterns: parsed.packagePatterns,
|
|
@@ -237,7 +244,8 @@ async function runDependencyCli(command, args, io = console, runtime = {}) {
|
|
|
237
244
|
failures: results.failures,
|
|
238
245
|
tags: project.policy.tags,
|
|
239
246
|
cliTag: parsed.tag,
|
|
240
|
-
|
|
247
|
+
mode: command === "upgrade" ? parsed.force ? "force" : "upgrade" : "update",
|
|
248
|
+
localVersions: project.localVersions
|
|
241
249
|
});
|
|
242
250
|
let applied = 0;
|
|
243
251
|
const errors = [];
|
package/package.json
CHANGED
package/dist/planner-VAj7qlxr.js
DELETED
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
import { n as isBreakingChange, r as rewriteRange, t as analyzeRange } from "./range-YUs9eimn.js";
|
|
2
|
-
import { t as parseDependencySpecification } from "./specification-DXnDOC-0.js";
|
|
3
|
-
import semver from "semver";
|
|
4
|
-
//#region src/update/planner.ts
|
|
5
|
-
const STATUS_PRIORITY = {
|
|
6
|
-
current: 0,
|
|
7
|
-
local: 1,
|
|
8
|
-
safe: 2,
|
|
9
|
-
breaking: 3,
|
|
10
|
-
manual: 4,
|
|
11
|
-
error: 5
|
|
12
|
-
};
|
|
13
|
-
function publishedVersions(packument) {
|
|
14
|
-
return Object.keys(packument.versions ?? {}).filter((version) => semver.valid(version) !== null).sort(semver.compare);
|
|
15
|
-
}
|
|
16
|
-
function selectedTarget(packument, tag) {
|
|
17
|
-
const value = packument["dist-tags"]?.[tag];
|
|
18
|
-
return typeof value === "string" && semver.valid(value) ? value : null;
|
|
19
|
-
}
|
|
20
|
-
function plannedOccurrence(occurrence, packument, failure, selectedTag, force) {
|
|
21
|
-
const base = {
|
|
22
|
-
workspace: occurrence.workspace,
|
|
23
|
-
manifestPath: occurrence.manifestPath,
|
|
24
|
-
relativeManifestPath: occurrence.relativeManifestPath,
|
|
25
|
-
section: occurrence.section,
|
|
26
|
-
currentSpecification: occurrence.currentSpecification,
|
|
27
|
-
proposedSpecification: null,
|
|
28
|
-
allowedVersion: null
|
|
29
|
-
};
|
|
30
|
-
if (occurrence.kind === "local") return {
|
|
31
|
-
targetVersion: null,
|
|
32
|
-
occurrence: {
|
|
33
|
-
...base,
|
|
34
|
-
status: "local",
|
|
35
|
-
reason: occurrence.reason
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
if (occurrence.kind === "manual") return {
|
|
39
|
-
targetVersion: null,
|
|
40
|
-
occurrence: {
|
|
41
|
-
...base,
|
|
42
|
-
status: "manual",
|
|
43
|
-
reason: occurrence.reason
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
if (failure) return {
|
|
47
|
-
targetVersion: null,
|
|
48
|
-
occurrence: {
|
|
49
|
-
...base,
|
|
50
|
-
status: "error",
|
|
51
|
-
reason: failure
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
if (occurrence.kind === "current") return {
|
|
55
|
-
targetVersion: packument ? selectedTarget(packument, selectedTag) : null,
|
|
56
|
-
occurrence: {
|
|
57
|
-
...base,
|
|
58
|
-
status: "current",
|
|
59
|
-
reason: occurrence.reason
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
if (!packument) return {
|
|
63
|
-
targetVersion: null,
|
|
64
|
-
occurrence: {
|
|
65
|
-
...base,
|
|
66
|
-
status: "error",
|
|
67
|
-
reason: "package metadata is unavailable"
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
const targetVersion = selectedTarget(packument, selectedTag);
|
|
71
|
-
if (!targetVersion) return {
|
|
72
|
-
targetVersion: null,
|
|
73
|
-
occurrence: {
|
|
74
|
-
...base,
|
|
75
|
-
status: "error",
|
|
76
|
-
reason: `dist-tag '${selectedTag}' is not published`
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
const versions = publishedVersions(packument);
|
|
80
|
-
if (!versions.includes(targetVersion)) return {
|
|
81
|
-
targetVersion: null,
|
|
82
|
-
occurrence: {
|
|
83
|
-
...base,
|
|
84
|
-
status: "error",
|
|
85
|
-
reason: `dist-tag '${selectedTag}' does not identify a published version`
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
const allowedVersion = semver.maxSatisfying(versions, occurrence.currentSpecification);
|
|
89
|
-
if (!allowedVersion) return {
|
|
90
|
-
targetVersion,
|
|
91
|
-
occurrence: {
|
|
92
|
-
...base,
|
|
93
|
-
status: "manual",
|
|
94
|
-
reason: "no published version satisfies the current specification"
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
if (semver.satisfies(targetVersion, occurrence.currentSpecification) || !semver.gt(targetVersion, allowedVersion)) return {
|
|
98
|
-
targetVersion,
|
|
99
|
-
occurrence: {
|
|
100
|
-
...base,
|
|
101
|
-
allowedVersion,
|
|
102
|
-
status: "current",
|
|
103
|
-
reason: "selected target is already covered by the current specification"
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
const analysis = analyzeRange(occurrence.currentSpecification);
|
|
107
|
-
if (!analysis.shape) return {
|
|
108
|
-
targetVersion,
|
|
109
|
-
occurrence: {
|
|
110
|
-
...base,
|
|
111
|
-
allowedVersion,
|
|
112
|
-
status: "manual",
|
|
113
|
-
reason: analysis.reason
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
const breaking = isBreakingChange(allowedVersion, targetVersion);
|
|
117
|
-
const eligible = !breaking || force;
|
|
118
|
-
return {
|
|
119
|
-
targetVersion,
|
|
120
|
-
occurrence: {
|
|
121
|
-
...base,
|
|
122
|
-
allowedVersion,
|
|
123
|
-
proposedSpecification: eligible ? rewriteRange(analysis.shape, targetVersion, breaking) : null,
|
|
124
|
-
status: breaking ? "breaking" : "safe",
|
|
125
|
-
reason: breaking ? eligible ? "latest version is eligible for askr upgrade" : "breaking update is available via askr upgrade" : "compatible update is available"
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
function occurrenceKey(occurrence, packageName) {
|
|
130
|
-
return [
|
|
131
|
-
occurrence.manifestPath,
|
|
132
|
-
occurrence.section,
|
|
133
|
-
packageName ?? occurrence.package,
|
|
134
|
-
occurrence.currentSpecification
|
|
135
|
-
].join("\0");
|
|
136
|
-
}
|
|
137
|
-
function resolveSpecificationVersion(specification, packument) {
|
|
138
|
-
const versions = publishedVersions(packument);
|
|
139
|
-
const parsed = parseDependencySpecification(specification);
|
|
140
|
-
if (parsed.type === "tag") {
|
|
141
|
-
const tagged = packument["dist-tags"]?.[parsed.rawSpec];
|
|
142
|
-
return typeof tagged === "string" && semver.valid(tagged) ? tagged : null;
|
|
143
|
-
}
|
|
144
|
-
if (parsed.type === "version") return semver.valid(parsed.rawSpec);
|
|
145
|
-
if (parsed.type === "range") return semver.maxSatisfying(versions, parsed.rawSpec);
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
function versionMetadata(packument, version) {
|
|
149
|
-
const metadata = packument.versions?.[version];
|
|
150
|
-
return metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : null;
|
|
151
|
-
}
|
|
152
|
-
function applyPeerCompatibilityGuard(decisions, contextOccurrences, packuments) {
|
|
153
|
-
const plannedByKey = /* @__PURE__ */ new Map();
|
|
154
|
-
for (const decision of decisions) for (const occurrence of decision.occurrences) plannedByKey.set(occurrenceKey(occurrence, decision.package), occurrence);
|
|
155
|
-
const contextByWorkspace = /* @__PURE__ */ new Map();
|
|
156
|
-
for (const occurrence of contextOccurrences) {
|
|
157
|
-
const entries = contextByWorkspace.get(occurrence.workspace) ?? [];
|
|
158
|
-
entries.push(occurrence);
|
|
159
|
-
contextByWorkspace.set(occurrence.workspace, entries);
|
|
160
|
-
}
|
|
161
|
-
for (const [workspace, context] of contextByWorkspace) {
|
|
162
|
-
const states = context.flatMap((dependency) => {
|
|
163
|
-
if (!dependency.registryManaged) return [];
|
|
164
|
-
const packument = packuments.get(dependency.package);
|
|
165
|
-
if (!packument) return [];
|
|
166
|
-
const planned = plannedByKey.get(occurrenceKey(dependency));
|
|
167
|
-
const currentVersion = resolveSpecificationVersion(dependency.currentSpecification, packument);
|
|
168
|
-
const futureVersion = resolveSpecificationVersion(planned?.proposedSpecification ?? dependency.currentSpecification, packument);
|
|
169
|
-
return currentVersion && futureVersion ? [{
|
|
170
|
-
dependency,
|
|
171
|
-
packument,
|
|
172
|
-
planned,
|
|
173
|
-
currentVersion,
|
|
174
|
-
futureVersion,
|
|
175
|
-
changed: Boolean(planned?.proposedSpecification)
|
|
176
|
-
}] : [];
|
|
177
|
-
});
|
|
178
|
-
const blockers = /* @__PURE__ */ new Map();
|
|
179
|
-
for (const provider of states) {
|
|
180
|
-
const currentPeers = versionMetadata(provider.packument, provider.currentVersion)?.peerDependencies ?? {};
|
|
181
|
-
const futurePeers = versionMetadata(provider.packument, provider.futureVersion)?.peerDependencies ?? {};
|
|
182
|
-
const peerNames = /* @__PURE__ */ new Set([...Object.keys(currentPeers), ...Object.keys(futurePeers)]);
|
|
183
|
-
for (const peerName of peerNames) {
|
|
184
|
-
const currentRequirement = currentPeers[peerName];
|
|
185
|
-
const futureRequirement = futurePeers[peerName];
|
|
186
|
-
if (futureRequirement !== void 0 && typeof futureRequirement !== "string") continue;
|
|
187
|
-
for (const peer of states.filter((state) => state.dependency.package === peerName)) {
|
|
188
|
-
const currentAccepted = typeof currentRequirement !== "string" || semver.satisfies(peer.currentVersion, currentRequirement);
|
|
189
|
-
const futureAccepted = typeof futureRequirement !== "string" || semver.satisfies(peer.futureVersion, futureRequirement);
|
|
190
|
-
if (!currentAccepted || futureAccepted) continue;
|
|
191
|
-
const reason = `${provider.dependency.package}@${provider.futureVersion} requires ${peerName}@${futureRequirement}`;
|
|
192
|
-
if (provider.changed && provider.planned) blockers.set(provider.planned, reason);
|
|
193
|
-
if (peer.changed && peer.planned) blockers.set(peer.planned, reason);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
for (const [planned, reason] of blockers) {
|
|
198
|
-
planned.status = "manual";
|
|
199
|
-
planned.proposedSpecification = null;
|
|
200
|
-
planned.reason = reason;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
function aggregateStatus(occurrences) {
|
|
205
|
-
return occurrences.reduce((status, occurrence) => STATUS_PRIORITY[occurrence.status] > STATUS_PRIORITY[status] ? occurrence.status : status, "current");
|
|
206
|
-
}
|
|
207
|
-
function summarize(decisions) {
|
|
208
|
-
const summary = {
|
|
209
|
-
packages: decisions.length,
|
|
210
|
-
occurrences: 0,
|
|
211
|
-
changedOccurrences: 0,
|
|
212
|
-
current: 0,
|
|
213
|
-
safe: 0,
|
|
214
|
-
breaking: 0,
|
|
215
|
-
local: 0,
|
|
216
|
-
manual: 0,
|
|
217
|
-
error: 0
|
|
218
|
-
};
|
|
219
|
-
for (const decision of decisions) {
|
|
220
|
-
decision.status = aggregateStatus(decision.occurrences);
|
|
221
|
-
decision.reason = decision.occurrences.find((entry) => entry.status === decision.status)?.reason ?? "";
|
|
222
|
-
summary[decision.status] += 1;
|
|
223
|
-
summary.occurrences += decision.occurrences.length;
|
|
224
|
-
summary.changedOccurrences += decision.occurrences.filter((entry) => entry.proposedSpecification).length;
|
|
225
|
-
}
|
|
226
|
-
return summary;
|
|
227
|
-
}
|
|
228
|
-
function planUpdates(options) {
|
|
229
|
-
const failures = options.failures ?? /* @__PURE__ */ new Map();
|
|
230
|
-
const tags = options.tags ?? {};
|
|
231
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
232
|
-
for (const occurrence of options.occurrences) {
|
|
233
|
-
const entries = grouped.get(occurrence.package) ?? [];
|
|
234
|
-
entries.push(occurrence);
|
|
235
|
-
grouped.set(occurrence.package, entries);
|
|
236
|
-
}
|
|
237
|
-
const decisions = [...grouped.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([packageName, occurrences]) => {
|
|
238
|
-
const selectedTag = options.cliTag ?? tags[packageName] ?? "latest";
|
|
239
|
-
const planned = occurrences.map((occurrence) => plannedOccurrence(occurrence, options.packuments.get(packageName), failures.get(packageName), selectedTag, options.force ?? false));
|
|
240
|
-
const targetVersion = planned.find((entry) => entry.targetVersion)?.targetVersion ?? null;
|
|
241
|
-
const plannedOccurrences = planned.map((entry) => entry.occurrence);
|
|
242
|
-
const status = aggregateStatus(plannedOccurrences);
|
|
243
|
-
return {
|
|
244
|
-
package: packageName,
|
|
245
|
-
selectedTag,
|
|
246
|
-
targetVersion,
|
|
247
|
-
status,
|
|
248
|
-
reason: plannedOccurrences.find((entry) => entry.status === status)?.reason ?? "",
|
|
249
|
-
occurrences: plannedOccurrences
|
|
250
|
-
};
|
|
251
|
-
});
|
|
252
|
-
const selectedNames = new Set(grouped.keys());
|
|
253
|
-
for (const [packageName, failure] of [...failures].sort(([left], [right]) => left.localeCompare(right))) {
|
|
254
|
-
if (selectedNames.has(packageName)) continue;
|
|
255
|
-
const context = (options.contextOccurrences ?? []).filter((occurrence) => occurrence.package === packageName).map((occurrence) => ({
|
|
256
|
-
workspace: occurrence.workspace,
|
|
257
|
-
manifestPath: occurrence.manifestPath,
|
|
258
|
-
relativeManifestPath: occurrence.relativeManifestPath,
|
|
259
|
-
section: occurrence.section,
|
|
260
|
-
currentSpecification: occurrence.currentSpecification,
|
|
261
|
-
proposedSpecification: null,
|
|
262
|
-
allowedVersion: null,
|
|
263
|
-
status: "error",
|
|
264
|
-
reason: `peer compatibility lookup failed: ${failure}`
|
|
265
|
-
}));
|
|
266
|
-
decisions.push({
|
|
267
|
-
package: packageName,
|
|
268
|
-
selectedTag: options.cliTag ?? tags[packageName] ?? "latest",
|
|
269
|
-
targetVersion: null,
|
|
270
|
-
status: "error",
|
|
271
|
-
reason: `peer compatibility lookup failed: ${failure}`,
|
|
272
|
-
occurrences: context
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
decisions.sort((left, right) => left.package.localeCompare(right.package));
|
|
276
|
-
applyPeerCompatibilityGuard(decisions, options.contextOccurrences ?? options.occurrences, options.packuments);
|
|
277
|
-
const summary = summarize(decisions);
|
|
278
|
-
return {
|
|
279
|
-
decisions,
|
|
280
|
-
summary,
|
|
281
|
-
hasErrors: summary.error > 0
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
//#endregion
|
|
285
|
-
export { planUpdates };
|