@dennisrepa/elevate 1.6.0 → 1.8.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/README.md +9 -6
- package/dist/chunk-4YCMSRA6.js +5817 -0
- package/dist/chunk-4YCMSRA6.js.map +1 -0
- package/dist/cli.js +138 -3599
- package/dist/cli.js.map +1 -1
- package/dist/config-DRANL4H4.js +13 -0
- package/dist/config-DRANL4H4.js.map +1 -0
- package/docs/de.md +105 -25
- package/docs/en.md +105 -25
- package/package.json +4 -2
package/dist/cli.js
CHANGED
|
@@ -1,3500 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ConfigError,
|
|
4
|
+
EcosystemFactory,
|
|
5
|
+
InstallError,
|
|
6
|
+
VERSION,
|
|
7
|
+
buildWorkspaceIndex,
|
|
8
|
+
classifyOrigin,
|
|
9
|
+
extractPreReleaseTag,
|
|
10
|
+
isPreReleaseVersion,
|
|
11
|
+
loadConfig,
|
|
12
|
+
matchesInternalScope
|
|
13
|
+
} from "./chunk-4YCMSRA6.js";
|
|
2
14
|
|
|
3
15
|
// src/index.tsx
|
|
4
16
|
import { render } from "ink";
|
|
5
17
|
|
|
6
|
-
// src/config.ts
|
|
7
|
-
import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
|
|
8
|
-
import { dirname as dirname10, join as join23, resolve as resolve14 } from "path";
|
|
9
|
-
|
|
10
|
-
// src/domain/version-policy.ts
|
|
11
|
-
import semver2 from "semver";
|
|
12
|
-
|
|
13
|
-
// src/domain/versions.ts
|
|
14
|
-
import semver from "semver";
|
|
15
|
-
var PRE_RELEASE = /(?:^|[.\-_+]|(?<=\d))(alpha|beta|rc|cr|milestone|preview|snapshot|ea|dev|pre|(?:a|b|m)(?=\d))(?=$|[.\-_+]|\d)/i;
|
|
16
|
-
function isPreReleaseVersion(version) {
|
|
17
|
-
return PRE_RELEASE.test(version);
|
|
18
|
-
}
|
|
19
|
-
function extractPreReleaseTag(version) {
|
|
20
|
-
const match = version.match(PRE_RELEASE);
|
|
21
|
-
if (!match) return void 0;
|
|
22
|
-
const token = match[1].toLowerCase();
|
|
23
|
-
if (token === "a") return "ALPHA";
|
|
24
|
-
if (token === "b") return "BETA";
|
|
25
|
-
if (token === "m") {
|
|
26
|
-
const number = version.slice((match.index ?? 0) + match[0].length).match(/^\d+/)?.[0];
|
|
27
|
-
return `M${number ?? ""}`;
|
|
28
|
-
}
|
|
29
|
-
return token.toUpperCase();
|
|
30
|
-
}
|
|
31
|
-
function cleanJavaVersion(version) {
|
|
32
|
-
const match = version.match(/^v?(\d+(?:\.\d+){0,2})/);
|
|
33
|
-
if (!match) return null;
|
|
34
|
-
return semver.coerce(match[1])?.version ?? null;
|
|
35
|
-
}
|
|
36
|
-
function diffVersions(from, to) {
|
|
37
|
-
const kind = semver.diff(from, to);
|
|
38
|
-
if (kind === "major" || kind === "premajor") return "major";
|
|
39
|
-
if (kind === "minor" || kind === "preminor") return "minor";
|
|
40
|
-
return "patch";
|
|
41
|
-
}
|
|
42
|
-
function rangePrefix(range) {
|
|
43
|
-
if (range.startsWith("^")) return "^";
|
|
44
|
-
if (range.startsWith("~")) return "~";
|
|
45
|
-
return "";
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// src/domain/version-policy.ts
|
|
49
|
-
function semverDiff(current, target, normalize) {
|
|
50
|
-
const to = normalize(target);
|
|
51
|
-
if (!to || !semver2.valid(current)) return "minor";
|
|
52
|
-
return semver2.eq(current, to) ? "patch" : diffVersions(current, to);
|
|
53
|
-
}
|
|
54
|
-
var npmVersionPolicy = {
|
|
55
|
-
normalize: (v) => semver2.valid(v),
|
|
56
|
-
rewrite: (current, target) => `${rangePrefix(current)}${target}`,
|
|
57
|
-
diff: (current, target) => semverDiff(current, target, (v) => semver2.valid(v)),
|
|
58
|
-
allowsManualTargets: true
|
|
59
|
-
};
|
|
60
|
-
var mavenVersionPolicy = {
|
|
61
|
-
normalize: cleanJavaVersion,
|
|
62
|
-
rewrite: (_current, target) => target,
|
|
63
|
-
diff: (current, target) => semverDiff(current, target, cleanJavaVersion),
|
|
64
|
-
allowsManualTargets: false
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// src/adapters/npm/npm-package-manager.ts
|
|
68
|
-
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
69
|
-
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
70
|
-
|
|
71
|
-
// src/adapters/shared/checkout.ts
|
|
72
|
-
import { existsSync } from "fs";
|
|
73
|
-
import { dirname, join, resolve } from "path";
|
|
74
|
-
function findCheckoutBoundary(startDir) {
|
|
75
|
-
let dir = resolve(startDir);
|
|
76
|
-
for (; ; ) {
|
|
77
|
-
if (existsSync(join(dir, ".git"))) return dir;
|
|
78
|
-
const parent = dirname(dir);
|
|
79
|
-
if (parent === dir) return void 0;
|
|
80
|
-
dir = parent;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// src/adapters/shared/process.ts
|
|
85
|
-
import { exec, spawn } from "child_process";
|
|
86
|
-
var UnsafeArgumentError = class extends Error {
|
|
87
|
-
constructor(argument) {
|
|
88
|
-
super(
|
|
89
|
-
`Cannot pass ${JSON.stringify(argument)} to the Windows command shell: it contains one of the characters " % ! ^ & | < > or a line break, which cmd.exe would interpret. If this is part of a directory or file name, rename it or move the project to a path without these characters.`
|
|
90
|
-
);
|
|
91
|
-
this.name = "UnsafeArgumentError";
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
var IS_WINDOWS = process.platform === "win32";
|
|
95
|
-
var WINDOWS_UNSAFE = /["%!^&|<>\r\n]/;
|
|
96
|
-
function runCommand(command, args, options) {
|
|
97
|
-
return new Promise((resolve16, reject) => {
|
|
98
|
-
const spawnOptions = { cwd: options.cwd, windowsHide: true, env: process.env };
|
|
99
|
-
let child2;
|
|
100
|
-
if (IS_WINDOWS) {
|
|
101
|
-
for (const arg of [command, ...args]) {
|
|
102
|
-
if (WINDOWS_UNSAFE.test(arg)) {
|
|
103
|
-
reject(new UnsafeArgumentError(arg));
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
const commandLine = [command, ...args].map(quoteWindows).join(" ");
|
|
108
|
-
child2 = spawn(commandLine, { ...spawnOptions, shell: true });
|
|
109
|
-
} else {
|
|
110
|
-
child2 = spawn(command, args, spawnOptions);
|
|
111
|
-
}
|
|
112
|
-
let stdout = "";
|
|
113
|
-
let stderr = "";
|
|
114
|
-
let timedOut = false;
|
|
115
|
-
child2.stdout.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
|
|
116
|
-
child2.stderr.setEncoding("utf8").on("data", (chunk) => stderr += chunk);
|
|
117
|
-
const timer = options.timeoutMs ? setTimeout(() => {
|
|
118
|
-
timedOut = true;
|
|
119
|
-
killTree(child2.pid, () => child2.kill());
|
|
120
|
-
}, options.timeoutMs) : void 0;
|
|
121
|
-
child2.on("error", (err) => {
|
|
122
|
-
if (timer) clearTimeout(timer);
|
|
123
|
-
reject(err);
|
|
124
|
-
});
|
|
125
|
-
child2.on("close", (code) => {
|
|
126
|
-
if (timer) clearTimeout(timer);
|
|
127
|
-
resolve16({ exitCode: code ?? 1, stdout, stderr, timedOut });
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
function runShellScript(script, options) {
|
|
132
|
-
return new Promise((resolve16) => {
|
|
133
|
-
exec(
|
|
134
|
-
script,
|
|
135
|
-
{ cwd: options.cwd, timeout: options.timeoutMs, maxBuffer: 64 * 1024 * 1024, windowsHide: true },
|
|
136
|
-
(error, stdout, stderr) => {
|
|
137
|
-
const timedOut = Boolean(error?.killed);
|
|
138
|
-
const exitCode = error ? typeof error.code === "number" ? error.code : 1 : 0;
|
|
139
|
-
resolve16({ exitCode, stdout, stderr, timedOut });
|
|
140
|
-
}
|
|
141
|
-
);
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
function killTree(pid, fallback) {
|
|
145
|
-
if (!IS_WINDOWS || pid === void 0) {
|
|
146
|
-
fallback();
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
const killer = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
|
|
150
|
-
killer.on("error", fallback);
|
|
151
|
-
}
|
|
152
|
-
function tail(output, lines = 15) {
|
|
153
|
-
return output.trim().split(/\r?\n/).slice(-lines).join("\n");
|
|
154
|
-
}
|
|
155
|
-
function quoteWindows(arg) {
|
|
156
|
-
return /^[\w.:\\/@+-]+$/.test(arg) ? arg : `"${arg}"`;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// src/adapters/npm/npm-package-manager.ts
|
|
160
|
-
var MARKER_FILES = [
|
|
161
|
-
["npm", ["package-lock.json", "npm-shrinkwrap.json"]],
|
|
162
|
-
["pnpm", ["pnpm-lock.yaml", "pnpm-workspace.yaml"]],
|
|
163
|
-
["yarn", ["yarn.lock", ".yarnrc.yml"]],
|
|
164
|
-
["bun", ["bun.lock", "bun.lockb"]]
|
|
165
|
-
];
|
|
166
|
-
var UnsupportedPackageManagerError = class extends Error {
|
|
167
|
-
constructor(detected) {
|
|
168
|
-
super(
|
|
169
|
-
`This repository is managed by ${detected.name} (${detected.evidence}). Elevate supports npm and pnpm for Node.js projects; using either here would create a lockfile next to ${detected.name}'s. Nothing was changed.`
|
|
170
|
-
);
|
|
171
|
-
this.name = "UnsupportedPackageManagerError";
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
function detectPackageManager(dir) {
|
|
175
|
-
const boundary = findCheckoutBoundary(dir);
|
|
176
|
-
let current = resolve2(dir);
|
|
177
|
-
for (; ; ) {
|
|
178
|
-
const found = detectIn(current);
|
|
179
|
-
if (found) return found;
|
|
180
|
-
const parent = dirname2(current);
|
|
181
|
-
if (boundary === void 0 || current === boundary || parent === current) break;
|
|
182
|
-
current = parent;
|
|
183
|
-
}
|
|
184
|
-
return { name: "npm", evidence: "no other package manager detected" };
|
|
185
|
-
}
|
|
186
|
-
function detectIn(dir) {
|
|
187
|
-
const declared = readPackageManagerField(dir);
|
|
188
|
-
const name = declared?.match(/^(npm|pnpm|yarn|bun)@/)?.[1];
|
|
189
|
-
if (declared && name) return { name, evidence: `"packageManager": "${declared}" in package.json` };
|
|
190
|
-
for (const [manager, files] of MARKER_FILES) {
|
|
191
|
-
const found = files.find((file) => existsSync2(join2(dir, file)));
|
|
192
|
-
if (found) return { name: manager, evidence: found };
|
|
193
|
-
}
|
|
194
|
-
return void 0;
|
|
195
|
-
}
|
|
196
|
-
function assertSupportedPackageManager(rootDir) {
|
|
197
|
-
const detected = detectPackageManager(rootDir);
|
|
198
|
-
if (detected.name === "npm" || detected.name === "pnpm") return detected.name;
|
|
199
|
-
throw new UnsupportedPackageManagerError(detected);
|
|
200
|
-
}
|
|
201
|
-
var PackageManagerNotFoundError = class extends Error {
|
|
202
|
-
constructor(reason, detected) {
|
|
203
|
-
super(
|
|
204
|
-
`pnpm could not be run in this repository (${reason}). This repository is managed by pnpm (${detected.evidence}); install pnpm or enable Corepack (corepack enable). Nothing was changed.`
|
|
205
|
-
);
|
|
206
|
-
this.name = "PackageManagerNotFoundError";
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
var VERSION_TIMEOUT_MS = 15e3;
|
|
210
|
-
var verifiedRoots = /* @__PURE__ */ new Set();
|
|
211
|
-
async function ensurePnpmRunnable(rootDir, detected) {
|
|
212
|
-
const key = resolve2(rootDir);
|
|
213
|
-
if (verifiedRoots.has(key)) return;
|
|
214
|
-
let reason;
|
|
215
|
-
try {
|
|
216
|
-
const result = await runCommand("pnpm", ["--version"], { cwd: rootDir, timeoutMs: VERSION_TIMEOUT_MS });
|
|
217
|
-
if (result.timedOut) reason = "pnpm --version timed out";
|
|
218
|
-
else if (result.exitCode !== 0) {
|
|
219
|
-
const line = firstLine(result.stderr) || firstLine(result.stdout);
|
|
220
|
-
reason = `pnpm --version exited with code ${result.exitCode}: ${line}`;
|
|
221
|
-
}
|
|
222
|
-
} catch {
|
|
223
|
-
reason = "command not found";
|
|
224
|
-
}
|
|
225
|
-
if (reason !== void 0) throw new PackageManagerNotFoundError(reason, detected);
|
|
226
|
-
verifiedRoots.add(key);
|
|
227
|
-
}
|
|
228
|
-
async function prepareSupportedPackageManager(rootDir) {
|
|
229
|
-
const manager = assertSupportedPackageManager(rootDir);
|
|
230
|
-
if (manager === "pnpm") await ensurePnpmRunnable(rootDir, detectPackageManager(rootDir));
|
|
231
|
-
return manager;
|
|
232
|
-
}
|
|
233
|
-
function firstLine(text) {
|
|
234
|
-
return text.split(/\r?\n/).find((l) => l.trim() !== "")?.trim() ?? "";
|
|
235
|
-
}
|
|
236
|
-
function readPackageManagerField(rootDir) {
|
|
237
|
-
try {
|
|
238
|
-
const field = JSON.parse(readFileSync(join2(rootDir, "package.json"), "utf8"))?.packageManager;
|
|
239
|
-
return typeof field === "string" ? field : void 0;
|
|
240
|
-
} catch {
|
|
241
|
-
return void 0;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// src/adapters/npm/npm-discovery.ts
|
|
246
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
247
|
-
import { dirname as dirname3, join as join4, relative, resolve as resolve3 } from "path";
|
|
248
|
-
import mapWorkspaces from "@npmcli/map-workspaces";
|
|
249
|
-
|
|
250
|
-
// src/adapters/npm/pnpm-workspace.ts
|
|
251
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
252
|
-
import { join as join3 } from "path";
|
|
253
|
-
import { isMap, isScalar, parse, parseDocument } from "yaml";
|
|
254
|
-
var PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
255
|
-
function isRecord(value) {
|
|
256
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
257
|
-
}
|
|
258
|
-
function entries(value) {
|
|
259
|
-
const map = /* @__PURE__ */ new Map();
|
|
260
|
-
if (isRecord(value)) {
|
|
261
|
-
for (const [name, range] of Object.entries(value)) {
|
|
262
|
-
if (typeof range === "string") map.set(name, range);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
return map;
|
|
266
|
-
}
|
|
267
|
-
function readPnpmWorkspace(rootDir) {
|
|
268
|
-
const file = join3(rootDir, PNPM_WORKSPACE_FILE);
|
|
269
|
-
let data;
|
|
270
|
-
try {
|
|
271
|
-
data = parse(readFileSync2(file, "utf8"));
|
|
272
|
-
} catch {
|
|
273
|
-
return void 0;
|
|
274
|
-
}
|
|
275
|
-
if (data !== null && !isRecord(data)) return void 0;
|
|
276
|
-
const doc = data ?? {};
|
|
277
|
-
const packages = Array.isArray(doc.packages) ? doc.packages.filter((p) => typeof p === "string") : [];
|
|
278
|
-
const catalogs = /* @__PURE__ */ new Map();
|
|
279
|
-
const named = isRecord(doc.catalogs) ? doc.catalogs : {};
|
|
280
|
-
for (const [name, content] of Object.entries(named)) catalogs.set(name, entries(content));
|
|
281
|
-
const hasTopLevel = isRecord(doc.catalog);
|
|
282
|
-
const hasNamedDefault = isRecord(named.default);
|
|
283
|
-
if (hasTopLevel) catalogs.set("default", entries(doc.catalog));
|
|
284
|
-
return { file, packages, catalogs, ambiguousDefaultCatalog: hasTopLevel && hasNamedDefault };
|
|
285
|
-
}
|
|
286
|
-
function setCatalogRange(source, catalog, name, newRange) {
|
|
287
|
-
const doc = parseDocument(source);
|
|
288
|
-
const holders = catalog === "default" ? [doc.get("catalog", true), doc.getIn(["catalogs", "default"], true)] : [doc.getIn(["catalogs", catalog], true)];
|
|
289
|
-
for (const holder of holders) {
|
|
290
|
-
if (!isMap(holder)) continue;
|
|
291
|
-
const node = holder.get(name, true);
|
|
292
|
-
if (!isScalar(node) || !node.range) continue;
|
|
293
|
-
const scalar = node;
|
|
294
|
-
const [start, end] = scalar.range;
|
|
295
|
-
const quote = scalar.type === "QUOTE_DOUBLE" ? '"' : scalar.type === "QUOTE_SINGLE" ? "'" : "";
|
|
296
|
-
return source.slice(0, start) + quote + newRange + quote + source.slice(end);
|
|
297
|
-
}
|
|
298
|
-
throw new Error(`No entry for '${name}' in catalog '${catalog}' of ${PNPM_WORKSPACE_FILE}.`);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// src/adapters/npm/npm-discovery.ts
|
|
302
|
-
function readJson(filePath) {
|
|
303
|
-
try {
|
|
304
|
-
return JSON.parse(readFileSync3(filePath, "utf8"));
|
|
305
|
-
} catch {
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
var NpmModuleDiscoveryAdapter = class {
|
|
310
|
-
/**
|
|
311
|
-
* The nearest directory, from `startDir` upwards to the boundary, whose
|
|
312
|
-
* package.json is valid JSON and declares `workspaces`, or that contains a
|
|
313
|
-
* valid `pnpm-workspace.yaml`.
|
|
314
|
-
*/
|
|
315
|
-
findRoot(startDir, boundaryDir) {
|
|
316
|
-
let dir = resolve3(startDir);
|
|
317
|
-
const boundary = boundaryDir ? resolve3(boundaryDir) : void 0;
|
|
318
|
-
for (; ; ) {
|
|
319
|
-
if (readJson(join4(dir, "package.json"))?.workspaces || readPnpmWorkspace(dir)) return dir;
|
|
320
|
-
const parent = dirname3(dir);
|
|
321
|
-
if (dir === boundary || parent === dir) return void 0;
|
|
322
|
-
dir = parent;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
async discover(rootDir) {
|
|
326
|
-
const rootPkg = readJson(join4(rootDir, "package.json"));
|
|
327
|
-
const modules = [
|
|
328
|
-
{
|
|
329
|
-
id: rootPkg?.name ?? "root",
|
|
330
|
-
name: rootPkg?.name ?? "Root Workspace (npm)",
|
|
331
|
-
path: rootDir,
|
|
332
|
-
relPath: "Root",
|
|
333
|
-
ecosystem: "npm",
|
|
334
|
-
isRoot: true,
|
|
335
|
-
version: typeof rootPkg?.version === "string" ? rootPkg.version : void 0
|
|
336
|
-
}
|
|
337
|
-
];
|
|
338
|
-
const patterns = detectPackageManager(rootDir).name === "pnpm" ? readPnpmWorkspace(rootDir)?.packages : rootPkg?.workspaces;
|
|
339
|
-
if (!patterns || Array.isArray(patterns) && patterns.length === 0) return modules;
|
|
340
|
-
let workspaces;
|
|
341
|
-
try {
|
|
342
|
-
workspaces = await mapWorkspaces({ cwd: rootDir, pkg: { ...rootPkg, workspaces: patterns } });
|
|
343
|
-
} catch {
|
|
344
|
-
return modules;
|
|
345
|
-
}
|
|
346
|
-
for (const [name, path] of workspaces) {
|
|
347
|
-
const pkg = readJson(join4(path, "package.json"));
|
|
348
|
-
modules.push({
|
|
349
|
-
id: name,
|
|
350
|
-
name,
|
|
351
|
-
path,
|
|
352
|
-
relPath: relative(rootDir, path).replace(/\\/g, "/"),
|
|
353
|
-
ecosystem: "npm",
|
|
354
|
-
isRoot: false,
|
|
355
|
-
version: typeof pkg?.version === "string" ? pkg.version : void 0
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
modules.sort((a, b) => a.isRoot ? -1 : b.isRoot ? 1 : a.relPath.localeCompare(b.relPath));
|
|
359
|
-
return modules;
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
// src/adapters/npm/npm-registry.ts
|
|
364
|
-
import semver3 from "semver";
|
|
365
|
-
|
|
366
|
-
// src/domain/origin.ts
|
|
367
|
-
function buildWorkspaceIndex(modules) {
|
|
368
|
-
const index = /* @__PURE__ */ new Map();
|
|
369
|
-
for (const module of modules) {
|
|
370
|
-
if (module.id && module.id !== "empty") index.set(module.id, module);
|
|
371
|
-
}
|
|
372
|
-
return index;
|
|
373
|
-
}
|
|
374
|
-
function classifyOrigin(identifier, ecosystem, context) {
|
|
375
|
-
const module = context.workspace.get(identifier);
|
|
376
|
-
if (module) {
|
|
377
|
-
return { kind: "workspace", moduleId: module.id, moduleRelPath: module.relPath };
|
|
378
|
-
}
|
|
379
|
-
if (matchesInternalScope(identifier, ecosystem, context.internalScopes)) {
|
|
380
|
-
return { kind: "private", matchedBy: "internal-scope" };
|
|
381
|
-
}
|
|
382
|
-
if (ecosystem === "npm" && context.scopedRegistries) {
|
|
383
|
-
const scope = npmScopeOf(identifier);
|
|
384
|
-
const registry = scope ? context.scopedRegistries.get(scope) : void 0;
|
|
385
|
-
if (registry) return { kind: "private", matchedBy: "scoped-registry", registry };
|
|
386
|
-
}
|
|
387
|
-
return { kind: "public" };
|
|
388
|
-
}
|
|
389
|
-
function matchesInternalScope(identifier, ecosystem, internalScopes) {
|
|
390
|
-
if (ecosystem === "maven") {
|
|
391
|
-
const groupId = identifier.split(":")[0] ?? "";
|
|
392
|
-
return internalScopes.some((entry) => {
|
|
393
|
-
const prefix = entry.replace(/^@/, "").replace(/[.:/]+$/, "");
|
|
394
|
-
return prefix.length > 0 && (groupId === prefix || groupId.startsWith(`${prefix}.`));
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
return internalScopes.some((entry) => {
|
|
398
|
-
if (entry.startsWith("@")) {
|
|
399
|
-
const scope = entry.replace(/\/+$/, "");
|
|
400
|
-
return identifier.startsWith(`${scope}/`);
|
|
401
|
-
}
|
|
402
|
-
if (entry.endsWith("*")) return identifier.startsWith(entry.slice(0, -1));
|
|
403
|
-
return identifier === entry;
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
function npmScopeOf(name) {
|
|
407
|
-
if (!name.startsWith("@")) return void 0;
|
|
408
|
-
const slash = name.indexOf("/");
|
|
409
|
-
return slash > 1 ? name.slice(0, slash) : void 0;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// src/adapters/npm/npm-commands.ts
|
|
413
|
-
function driverFor(dir) {
|
|
414
|
-
return detectPackageManager(dir).name === "pnpm" ? "pnpm" : "npm";
|
|
415
|
-
}
|
|
416
|
-
var commands = {
|
|
417
|
-
/** Install after Elevate wrote new ranges. pnpm is told explicitly not to freeze: `CI` would otherwise imply it. */
|
|
418
|
-
install: (m) => m === "pnpm" ? { command: "pnpm", args: ["install", "--no-frozen-lockfile"] } : { command: "npm", args: ["install"] },
|
|
419
|
-
/** Restore the installed state after a rollback. */
|
|
420
|
-
restore: (m) => m === "pnpm" ? { command: "pnpm", args: ["install", "--frozen-lockfile"] } : { command: "npm", args: ["install"] },
|
|
421
|
-
/** Default verification. */
|
|
422
|
-
verify: (m) => m === "pnpm" ? { command: "pnpm", args: ["install", "--frozen-lockfile", "--lockfile-only"] } : { command: "npm", args: ["ls"] },
|
|
423
|
-
view: (m, name, field) => ({ command: m, args: ["view", name, field, "--json"] }),
|
|
424
|
-
config: (m) => ({ command: m, args: ["config", "list", "--json"] })
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
// src/adapters/npm/npm-config.ts
|
|
428
|
-
var DEFAULT_REGISTRY = "https://registry.npmjs.org/";
|
|
429
|
-
var PUBLIC_NPM_HOSTS = /* @__PURE__ */ new Set(["registry.npmjs.org", "registry.npmjs.com", "registry.yarnpkg.com", "npm.jsr.io"]);
|
|
430
|
-
var NpmConfigReader = class {
|
|
431
|
-
cache = /* @__PURE__ */ new Map();
|
|
432
|
-
/** Returns the registry configuration that applies in `cwd` (cached per directory). */
|
|
433
|
-
read(cwd) {
|
|
434
|
-
let pending = this.cache.get(cwd);
|
|
435
|
-
if (!pending) {
|
|
436
|
-
pending = loadConfig(cwd);
|
|
437
|
-
this.cache.set(cwd, pending);
|
|
438
|
-
}
|
|
439
|
-
return pending;
|
|
440
|
-
}
|
|
441
|
-
/** The registry npm would contact for this package. */
|
|
442
|
-
async registryFor(packageName, cwd) {
|
|
443
|
-
const config = await this.read(cwd);
|
|
444
|
-
const scope = npmScopeOf(packageName);
|
|
445
|
-
return scope && config.scopedRegistries.get(scope) || config.registry;
|
|
446
|
-
}
|
|
447
|
-
};
|
|
448
|
-
function isPublicNpmRegistry(url) {
|
|
449
|
-
try {
|
|
450
|
-
return PUBLIC_NPM_HOSTS.has(new URL(url).hostname.toLowerCase());
|
|
451
|
-
} catch {
|
|
452
|
-
return false;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
function parseNpmConfig(json) {
|
|
456
|
-
const scopedRegistries = /* @__PURE__ */ new Map();
|
|
457
|
-
for (const [key, value] of Object.entries(json)) {
|
|
458
|
-
const match = key.match(/^(@[^:]+):registry$/);
|
|
459
|
-
if (match && typeof value === "string" && value) scopedRegistries.set(match[1], value);
|
|
460
|
-
}
|
|
461
|
-
const registry = typeof json.registry === "string" && json.registry ? json.registry : DEFAULT_REGISTRY;
|
|
462
|
-
return { registry, scopedRegistries };
|
|
463
|
-
}
|
|
464
|
-
async function loadConfig(cwd) {
|
|
465
|
-
try {
|
|
466
|
-
const { command, args } = commands.config(driverFor(cwd));
|
|
467
|
-
const result = await runCommand(command, args, { cwd, timeoutMs: 15e3 });
|
|
468
|
-
if (result.exitCode !== 0) return parseNpmConfig({});
|
|
469
|
-
return parseNpmConfig(JSON.parse(result.stdout));
|
|
470
|
-
} catch {
|
|
471
|
-
return parseNpmConfig({});
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// src/adapters/npm/npm-registry.ts
|
|
476
|
-
var LOOKUP_TIMEOUT_MS = 15e3;
|
|
477
|
-
var PACKAGE_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i;
|
|
478
|
-
function isValidPackageName(name) {
|
|
479
|
-
return name.length <= 214 && PACKAGE_NAME.test(name);
|
|
480
|
-
}
|
|
481
|
-
var NpmRegistryAdapter = class {
|
|
482
|
-
constructor(config = new NpmConfigReader()) {
|
|
483
|
-
this.config = config;
|
|
484
|
-
}
|
|
485
|
-
config;
|
|
486
|
-
async resolveEndpoint(coordinate, cwd) {
|
|
487
|
-
const url = await this.config.registryFor(coordinate.identifier, cwd);
|
|
488
|
-
return { url, isPublic: isPublicNpmRegistry(url) };
|
|
489
|
-
}
|
|
490
|
-
async getLatestVersion(coordinate, channel = "stable", cwd = process.cwd()) {
|
|
491
|
-
if (!isValidPackageName(coordinate.identifier)) return null;
|
|
492
|
-
if (channel === "stable") {
|
|
493
|
-
const latest = await this.view(coordinate.identifier, "dist-tags.latest", cwd);
|
|
494
|
-
if (typeof latest === "string" && semver3.valid(latest) && semver3.prerelease(latest) === null) {
|
|
495
|
-
return latest;
|
|
496
|
-
}
|
|
497
|
-
const releases = (await this.getAllVersions(coordinate, cwd)).filter(
|
|
498
|
-
(v) => semver3.valid(v) && semver3.prerelease(v) === null
|
|
499
|
-
);
|
|
500
|
-
return releases.length > 0 ? semver3.rsort(releases)[0] : null;
|
|
501
|
-
}
|
|
502
|
-
const version = await this.view(coordinate.identifier, "version", cwd);
|
|
503
|
-
return typeof version === "string" && version ? version : null;
|
|
504
|
-
}
|
|
505
|
-
async getAllVersions(coordinate, cwd = process.cwd()) {
|
|
506
|
-
if (!isValidPackageName(coordinate.identifier)) return [];
|
|
507
|
-
const versions = await this.view(coordinate.identifier, "versions", cwd);
|
|
508
|
-
const list = Array.isArray(versions) ? versions : versions ? [versions] : [];
|
|
509
|
-
return list.map(String).reverse();
|
|
510
|
-
}
|
|
511
|
-
/** Runs `npm view <name> <field> --json` (`pnpm view` in a pnpm repository); resolves null on any failure. */
|
|
512
|
-
async view(name, field, cwd) {
|
|
513
|
-
try {
|
|
514
|
-
const { command, args } = commands.view(driverFor(cwd), name, field);
|
|
515
|
-
const result = await runCommand(command, args, {
|
|
516
|
-
cwd,
|
|
517
|
-
timeoutMs: LOOKUP_TIMEOUT_MS
|
|
518
|
-
});
|
|
519
|
-
if (result.exitCode !== 0 || !result.stdout.trim()) return null;
|
|
520
|
-
return JSON.parse(result.stdout);
|
|
521
|
-
} catch {
|
|
522
|
-
return null;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
// src/adapters/npm/npm-scanner.ts
|
|
528
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
529
|
-
import { join as join5 } from "path";
|
|
530
|
-
import semver4 from "semver";
|
|
531
|
-
|
|
532
|
-
// src/adapters/shared/pool.ts
|
|
533
|
-
async function mapPooled(items, concurrency, fn) {
|
|
534
|
-
const results = new Array(items.length);
|
|
535
|
-
let next = 0;
|
|
536
|
-
async function worker() {
|
|
537
|
-
while (next < items.length) {
|
|
538
|
-
const index = next++;
|
|
539
|
-
results[index] = await fn(items[index]);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker());
|
|
543
|
-
await Promise.all(workers);
|
|
544
|
-
return results;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// src/adapters/npm/npm-protocols.ts
|
|
548
|
-
function splitRangeProtocol(range) {
|
|
549
|
-
if (range.startsWith("workspace:")) return { protocol: "workspace", rest: range.slice("workspace:".length) };
|
|
550
|
-
if (range.startsWith("catalog:")) return { protocol: "catalog", rest: range.slice("catalog:".length) };
|
|
551
|
-
return { protocol: void 0, rest: range };
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// src/adapters/npm/npm-scanner.ts
|
|
555
|
-
var CONCURRENCY = 6;
|
|
556
|
-
var DIFF_ORDER = { major: 0, minor: 1, patch: 2 };
|
|
557
|
-
var NpmDependencyAdapter = class {
|
|
558
|
-
constructor(registry, config) {
|
|
559
|
-
this.registry = registry;
|
|
560
|
-
this.config = config;
|
|
561
|
-
}
|
|
562
|
-
registry;
|
|
563
|
-
config;
|
|
564
|
-
async scan(module, context) {
|
|
565
|
-
const manager = await prepareSupportedPackageManager(context.rootDir);
|
|
566
|
-
const pnpm = manager === "pnpm";
|
|
567
|
-
let pkg;
|
|
568
|
-
try {
|
|
569
|
-
pkg = JSON.parse(readFileSync4(join5(module.path, "package.json"), "utf8"));
|
|
570
|
-
} catch {
|
|
571
|
-
return { candidates: [], skipped: [] };
|
|
572
|
-
}
|
|
573
|
-
const npmConfig = await this.config.read(context.rootDir);
|
|
574
|
-
const originContext = {
|
|
575
|
-
workspace: context.workspace,
|
|
576
|
-
internalScopes: context.internalScopes,
|
|
577
|
-
// A scope mapped to a public registry is not internal; only private
|
|
578
|
-
// mappings mark their scope as private.
|
|
579
|
-
scopedRegistries: new Map(
|
|
580
|
-
[...npmConfig.scopedRegistries].filter(([, url]) => !isPublicNpmRegistry(url))
|
|
581
|
-
)
|
|
582
|
-
};
|
|
583
|
-
const workspaceFile = pnpm ? readPnpmWorkspace(context.rootDir) : void 0;
|
|
584
|
-
const outcomes = await mapPooled(declaredDependencies(pkg), CONCURRENCY, async (dep) => {
|
|
585
|
-
const origin = classifyOrigin(dep.name, "npm", originContext);
|
|
586
|
-
if (origin.kind === "workspace") {
|
|
587
|
-
const target = context.workspace.get(dep.name);
|
|
588
|
-
if (pnpm && isUpgradableRange(dep.range) && splitRangeProtocol(dep.range).protocol === void 0) {
|
|
589
|
-
return { skipped: { identifier: dep.name, origin: "workspace", reason: "not-linked", detail: dep.range } };
|
|
590
|
-
}
|
|
591
|
-
const candidate = target ? alignmentCandidate(dep, target, origin, pnpm) : null;
|
|
592
|
-
return candidate ? { candidate } : null;
|
|
593
|
-
}
|
|
594
|
-
if (pnpm && splitRangeProtocol(dep.range).protocol === "catalog") {
|
|
595
|
-
return this.catalogOutcome(dep, origin, context, module.path, workspaceFile);
|
|
596
|
-
}
|
|
597
|
-
return this.registryOutcome(dep, origin, context);
|
|
598
|
-
});
|
|
599
|
-
const candidates = [];
|
|
600
|
-
const skipped = [];
|
|
601
|
-
for (const outcome of outcomes) {
|
|
602
|
-
if (!outcome) continue;
|
|
603
|
-
if ("candidate" in outcome) candidates.push(outcome.candidate);
|
|
604
|
-
else skipped.push(outcome.skipped);
|
|
605
|
-
}
|
|
606
|
-
candidates.sort((a, b) => (DIFF_ORDER[a.diff] ?? 3) - (DIFF_ORDER[b.diff] ?? 3));
|
|
607
|
-
return { candidates, skipped };
|
|
608
|
-
}
|
|
609
|
-
/** A `catalog:` dependency: the range comes from pnpm-workspace.yaml and is written back there. */
|
|
610
|
-
async catalogOutcome(dep, origin, context, modulePath, workspaceFile) {
|
|
611
|
-
const catalogName = splitRangeProtocol(dep.range).rest || "default";
|
|
612
|
-
const unresolved = (detail) => ({
|
|
613
|
-
skipped: { identifier: dep.name, origin: origin.kind, reason: "catalog-unresolved", detail }
|
|
614
|
-
});
|
|
615
|
-
if (!workspaceFile) return unresolved("pnpm-workspace.yaml is missing or not valid YAML");
|
|
616
|
-
if (catalogName === "default" && workspaceFile.ambiguousDefaultCatalog) {
|
|
617
|
-
return unresolved("catalog 'default' is defined twice (catalog and catalogs.default)");
|
|
618
|
-
}
|
|
619
|
-
const catalog = workspaceFile.catalogs.get(catalogName);
|
|
620
|
-
if (!catalog) return unresolved(`no catalog '${catalogName}'`);
|
|
621
|
-
const range = catalog.get(dep.name);
|
|
622
|
-
if (range === void 0) return unresolved(`no entry for '${dep.name}' in catalog '${catalogName}'`);
|
|
623
|
-
const declaration = {
|
|
624
|
-
file: workspaceFile.file,
|
|
625
|
-
kind: "catalog",
|
|
626
|
-
catalogName,
|
|
627
|
-
displayPath: "pnpm-workspace.yaml"
|
|
628
|
-
};
|
|
629
|
-
const sharedWith = usersOfCatalogEntry(context, modulePath, dep.name, catalogName);
|
|
630
|
-
return this.registryOutcome({ ...dep, range }, origin, context, {
|
|
631
|
-
declaration,
|
|
632
|
-
...sharedWith.length > 0 ? { sharedWith } : {}
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
/** Looks up a private or public dependency in its registry. */
|
|
636
|
-
async registryOutcome(dep, origin, context, extra = {}) {
|
|
637
|
-
if (!isUpgradableRange(dep.range)) return null;
|
|
638
|
-
if (!isValidPackageName(dep.name)) {
|
|
639
|
-
return { skipped: { identifier: dep.name, origin: origin.kind, reason: "invalid-name" } };
|
|
640
|
-
}
|
|
641
|
-
const coordinate = { identifier: dep.name, artifact: dep.name, ecosystem: "npm" };
|
|
642
|
-
let resolvedOrigin = origin;
|
|
643
|
-
if (origin.kind === "private") {
|
|
644
|
-
const endpoint = await this.registry.resolveEndpoint(coordinate, context.rootDir);
|
|
645
|
-
if (endpoint.isPublic) {
|
|
646
|
-
return {
|
|
647
|
-
skipped: {
|
|
648
|
-
identifier: dep.name,
|
|
649
|
-
origin: "private",
|
|
650
|
-
reason: "private-on-public-registry",
|
|
651
|
-
detail: endpoint.url
|
|
652
|
-
}
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
resolvedOrigin = { ...origin, registry: endpoint.url };
|
|
656
|
-
}
|
|
657
|
-
const latest = await this.registry.getLatestVersion(coordinate, context.channel, context.rootDir);
|
|
658
|
-
if (!latest) {
|
|
659
|
-
return { skipped: { identifier: dep.name, origin: origin.kind, reason: "lookup-failed" } };
|
|
660
|
-
}
|
|
661
|
-
const currentClean = semver4.minVersion(dep.range)?.version;
|
|
662
|
-
if (!currentClean || !semver4.valid(latest) || !semver4.gt(latest, currentClean)) return null;
|
|
663
|
-
const tag = semver4.prerelease(latest) ? extractPreReleaseTag(latest) ?? "PRE" : void 0;
|
|
664
|
-
const diff = diffVersions(currentClean, latest);
|
|
665
|
-
return {
|
|
666
|
-
candidate: {
|
|
667
|
-
coordinate,
|
|
668
|
-
currentRange: dep.range,
|
|
669
|
-
currentClean,
|
|
670
|
-
latest,
|
|
671
|
-
newRange: `${rangePrefix(dep.range)}${latest}`,
|
|
672
|
-
diff,
|
|
673
|
-
scope: dep.scope,
|
|
674
|
-
selected: diff !== "major",
|
|
675
|
-
origin: resolvedOrigin,
|
|
676
|
-
action: "update",
|
|
677
|
-
isPreRelease: tag !== void 0,
|
|
678
|
-
preReleaseTag: tag,
|
|
679
|
-
...extra
|
|
680
|
-
}
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
|
-
var DEPENDENCY_SECTIONS = ["dependencies", "optionalDependencies", "devDependencies"];
|
|
685
|
-
function declaredDependencies(pkg) {
|
|
686
|
-
const deps = /* @__PURE__ */ new Map();
|
|
687
|
-
for (const section of DEPENDENCY_SECTIONS) {
|
|
688
|
-
const scope = section === "devDependencies" ? "dev" : "prod";
|
|
689
|
-
for (const [name, range] of Object.entries(pkg[section] ?? {})) {
|
|
690
|
-
if (typeof range === "string") deps.set(name, { name, range, scope });
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
return [...deps.values()];
|
|
694
|
-
}
|
|
695
|
-
function isUpgradableRange(range) {
|
|
696
|
-
const trimmed = range.trim();
|
|
697
|
-
if (trimmed === "" || trimmed === "*" || trimmed.toLowerCase() === "x") return false;
|
|
698
|
-
return semver4.validRange(trimmed) !== null;
|
|
699
|
-
}
|
|
700
|
-
function alignmentCandidate(dep, target, origin, pnpm = false) {
|
|
701
|
-
const split = pnpm ? splitRangeProtocol(dep.range) : { protocol: void 0, rest: dep.range };
|
|
702
|
-
const range = split.protocol === "workspace" ? split.rest : dep.range;
|
|
703
|
-
const protocol = split.protocol === "workspace" ? "workspace:" : "";
|
|
704
|
-
const local = target.version;
|
|
705
|
-
if (!local || !semver4.valid(local) || !isUpgradableRange(range)) return null;
|
|
706
|
-
if (semver4.satisfies(local, range, { includePrerelease: true })) return null;
|
|
707
|
-
const currentClean = semver4.minVersion(range)?.version ?? "0.0.0";
|
|
708
|
-
const diff = diffVersions(currentClean, local);
|
|
709
|
-
const tag = semver4.prerelease(local) ? extractPreReleaseTag(local) ?? "PRE" : void 0;
|
|
710
|
-
return {
|
|
711
|
-
coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "npm" },
|
|
712
|
-
currentRange: dep.range,
|
|
713
|
-
currentClean,
|
|
714
|
-
latest: local,
|
|
715
|
-
newRange: `${protocol}${rangePrefix(range)}${local}`,
|
|
716
|
-
diff,
|
|
717
|
-
scope: dep.scope,
|
|
718
|
-
selected: diff !== "major",
|
|
719
|
-
origin,
|
|
720
|
-
action: "align",
|
|
721
|
-
availableVersions: [local],
|
|
722
|
-
isPreRelease: tag !== void 0,
|
|
723
|
-
preReleaseTag: tag
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
function usersOfCatalogEntry(context, selfPath, name, catalogName) {
|
|
727
|
-
const users = [];
|
|
728
|
-
for (const module of context.workspace.values()) {
|
|
729
|
-
if (module.path === selfPath) continue;
|
|
730
|
-
let pkg;
|
|
731
|
-
try {
|
|
732
|
-
pkg = JSON.parse(readFileSync4(join5(module.path, "package.json"), "utf8"));
|
|
733
|
-
} catch {
|
|
734
|
-
continue;
|
|
735
|
-
}
|
|
736
|
-
const dep = declaredDependencies(pkg).find((d) => d.name === name);
|
|
737
|
-
if (!dep || splitRangeProtocol(dep.range).protocol !== "catalog") continue;
|
|
738
|
-
if ((splitRangeProtocol(dep.range).rest || "default") !== catalogName) continue;
|
|
739
|
-
users.push(module.id);
|
|
740
|
-
}
|
|
741
|
-
return users;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// src/adapters/npm/npm-updater.ts
|
|
745
|
-
import { readFileSync as readFileSync7, writeFileSync } from "fs";
|
|
746
|
-
import { join as join8, relative as relative2 } from "path";
|
|
747
|
-
|
|
748
|
-
// src/domain/ports.ts
|
|
749
|
-
var InstallError = class extends Error {
|
|
750
|
-
constructor(message2, output) {
|
|
751
|
-
super(message2);
|
|
752
|
-
this.output = output;
|
|
753
|
-
this.name = "InstallError";
|
|
754
|
-
}
|
|
755
|
-
output;
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
// src/adapters/npm/npm-lockfile.ts
|
|
759
|
-
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
760
|
-
import { join as join6 } from "path";
|
|
761
|
-
function readLockfile(rootDir) {
|
|
762
|
-
const file = join6(rootDir, "package-lock.json");
|
|
763
|
-
if (!existsSync3(file)) return void 0;
|
|
764
|
-
try {
|
|
765
|
-
return JSON.parse(readFileSync5(file, "utf8"));
|
|
766
|
-
} catch {
|
|
767
|
-
return void 0;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
function findUnlinkedWorkspaceDependencies(lockfile, moduleRelPath, names) {
|
|
771
|
-
const packages = lockfile.packages ?? {};
|
|
772
|
-
const prefix = moduleRelPath ? `${moduleRelPath}/` : "";
|
|
773
|
-
const violations = [];
|
|
774
|
-
for (const name of names) {
|
|
775
|
-
const entry = packages[`${prefix}node_modules/${name}`] ?? packages[`node_modules/${name}`];
|
|
776
|
-
if (!entry) {
|
|
777
|
-
violations.push(`${name}: not present in package-lock.json`);
|
|
778
|
-
} else if (!entry.link) {
|
|
779
|
-
const source = entry.resolved ?? "unknown source";
|
|
780
|
-
violations.push(`${name}: installed from ${source} instead of linking the workspace package`);
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
return violations;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
// src/adapters/npm/pnpm-audit.ts
|
|
787
|
-
var AUDIT_TIMEOUT_MS = 6e4;
|
|
788
|
-
var LEVELS = ["info", "low", "moderate", "high", "critical"];
|
|
789
|
-
function unavailable(reason) {
|
|
790
|
-
return { message: `Vulnerability check unavailable: ${reason}.`, severity: "warn" };
|
|
791
|
-
}
|
|
792
|
-
function parsePnpmAudit(output) {
|
|
793
|
-
let json;
|
|
794
|
-
try {
|
|
795
|
-
json = JSON.parse(output);
|
|
796
|
-
} catch {
|
|
797
|
-
return unavailable("pnpm audit returned no result");
|
|
798
|
-
}
|
|
799
|
-
const counts = json?.metadata?.vulnerabilities;
|
|
800
|
-
if (counts && typeof counts === "object") {
|
|
801
|
-
const parts = [];
|
|
802
|
-
let total = 0;
|
|
803
|
-
for (const level of LEVELS) {
|
|
804
|
-
const n = Number(counts[level]) || 0;
|
|
805
|
-
total += n;
|
|
806
|
-
if (n > 0) parts.push(`${n} ${level}`);
|
|
807
|
-
}
|
|
808
|
-
if (total === 0) return { message: "No known vulnerabilities found.", severity: "clean" };
|
|
809
|
-
return {
|
|
810
|
-
message: `${total} ${total === 1 ? "vulnerability" : "vulnerabilities"} (${parts.join(", ")})`,
|
|
811
|
-
severity: "warn"
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
const errorMessage = json?.error?.message;
|
|
815
|
-
if (typeof errorMessage === "string" && errorMessage) return unavailable(errorMessage);
|
|
816
|
-
return unavailable("pnpm audit returned no result");
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
// src/adapters/npm/pnpm-lockfile.ts
|
|
820
|
-
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
821
|
-
import { join as join7 } from "path";
|
|
822
|
-
import { parse as parse2 } from "yaml";
|
|
823
|
-
var SECTIONS = ["dependencies", "devDependencies", "optionalDependencies"];
|
|
824
|
-
function readPnpmLockfile(rootDir) {
|
|
825
|
-
const file = join7(rootDir, "pnpm-lock.yaml");
|
|
826
|
-
if (!existsSync4(file)) return { kind: "missing" };
|
|
827
|
-
let data;
|
|
828
|
-
try {
|
|
829
|
-
data = parse2(readFileSync6(file, "utf8"));
|
|
830
|
-
} catch {
|
|
831
|
-
return { kind: "unreadable" };
|
|
832
|
-
}
|
|
833
|
-
if (typeof data !== "object" || data === null || Array.isArray(data)) return { kind: "unreadable" };
|
|
834
|
-
const lockfileVersion = String(data.lockfileVersion ?? "");
|
|
835
|
-
if (!/^9(\.|$)/.test(lockfileVersion)) return { kind: "unsupported", lockfileVersion };
|
|
836
|
-
const importers = data.importers ?? {};
|
|
837
|
-
if (typeof importers !== "object" || Array.isArray(importers)) return { kind: "unreadable" };
|
|
838
|
-
return { kind: "ok", importers };
|
|
839
|
-
}
|
|
840
|
-
function findUnlinkedPnpmDependencies(lockfile, moduleRelPath, names) {
|
|
841
|
-
switch (lockfile.kind) {
|
|
842
|
-
case "missing":
|
|
843
|
-
return ["pnpm-lock.yaml is missing; workspace links cannot be verified"];
|
|
844
|
-
case "unreadable":
|
|
845
|
-
return ["pnpm-lock.yaml could not be read; workspace links cannot be verified"];
|
|
846
|
-
case "unsupported":
|
|
847
|
-
return [
|
|
848
|
-
`pnpm-lock.yaml has lockfileVersion '${lockfile.lockfileVersion}', which Elevate cannot read; workspace links cannot be verified`
|
|
849
|
-
];
|
|
850
|
-
}
|
|
851
|
-
const importer = lockfile.importers[moduleRelPath === "" ? "." : moduleRelPath] ?? {};
|
|
852
|
-
const violations = [];
|
|
853
|
-
for (const name of names) {
|
|
854
|
-
let entry;
|
|
855
|
-
for (const section of SECTIONS) {
|
|
856
|
-
entry = importer[section]?.[name];
|
|
857
|
-
if (entry) break;
|
|
858
|
-
}
|
|
859
|
-
if (!entry) violations.push(`${name}: not present in pnpm-lock.yaml`);
|
|
860
|
-
else if (!String(entry.version).startsWith("link:")) {
|
|
861
|
-
violations.push(`${name}: resolved to ${entry.version} instead of linking the workspace package`);
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
return violations;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
// src/adapters/npm/npm-updater.ts
|
|
868
|
-
var INSTALL_TIMEOUT_MS = 10 * 6e4;
|
|
869
|
-
function failureDetails(manager, result) {
|
|
870
|
-
return manager === "pnpm" ? tail(`${result.stdout}
|
|
871
|
-
${result.stderr}`) : tail(result.stderr || result.stdout);
|
|
872
|
-
}
|
|
873
|
-
var NpmUpdaterAdapter = class {
|
|
874
|
-
affectedFiles(module, rootDir) {
|
|
875
|
-
if (detectPackageManager(rootDir).name === "pnpm") {
|
|
876
|
-
return [
|
|
877
|
-
join8(module.path, "package.json"),
|
|
878
|
-
join8(rootDir, "package.json"),
|
|
879
|
-
join8(rootDir, "pnpm-lock.yaml"),
|
|
880
|
-
join8(rootDir, "pnpm-workspace.yaml")
|
|
881
|
-
];
|
|
882
|
-
}
|
|
883
|
-
return [
|
|
884
|
-
join8(module.path, "package.json"),
|
|
885
|
-
join8(rootDir, "package.json"),
|
|
886
|
-
join8(rootDir, "package-lock.json"),
|
|
887
|
-
join8(rootDir, "npm-shrinkwrap.json")
|
|
888
|
-
];
|
|
889
|
-
}
|
|
890
|
-
async applyUpdates(module, rootDir, updates, onProgress) {
|
|
891
|
-
let manager;
|
|
892
|
-
try {
|
|
893
|
-
manager = await prepareSupportedPackageManager(rootDir);
|
|
894
|
-
} catch (err) {
|
|
895
|
-
if (err instanceof UnsupportedPackageManagerError || err instanceof PackageManagerNotFoundError) {
|
|
896
|
-
throw new InstallError(err.message);
|
|
897
|
-
}
|
|
898
|
-
throw err;
|
|
899
|
-
}
|
|
900
|
-
const catalogUpdates = updates.filter((u) => u.declaration?.kind === "catalog");
|
|
901
|
-
const manifestUpdates = updates.filter((u) => u.declaration?.kind !== "catalog");
|
|
902
|
-
const pkgPath = join8(module.path, "package.json");
|
|
903
|
-
let manifest;
|
|
904
|
-
if (manifestUpdates.length > 0 || catalogUpdates.length === 0) {
|
|
905
|
-
try {
|
|
906
|
-
const source = readFileSync7(pkgPath, "utf8");
|
|
907
|
-
manifest = { source, pkg: JSON.parse(source) };
|
|
908
|
-
} catch {
|
|
909
|
-
throw new InstallError(`Cannot read package.json in ${module.path}.`);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
if (manifest) {
|
|
913
|
-
onProgress("Writing new versions to package.json\u2026");
|
|
914
|
-
for (const item of manifestUpdates) {
|
|
915
|
-
const name = item.coordinate.artifact;
|
|
916
|
-
for (const section of DEPENDENCY_SECTIONS) {
|
|
917
|
-
if (typeof manifest.pkg[section]?.[name] === "string") manifest.pkg[section][name] = item.newRange;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
writeFileSync(pkgPath, serializeLike(manifest.source, manifest.pkg), "utf8");
|
|
921
|
-
}
|
|
922
|
-
if (catalogUpdates.length > 0) {
|
|
923
|
-
onProgress("Writing new versions to pnpm-workspace.yaml\u2026");
|
|
924
|
-
writeCatalogRanges(catalogUpdates);
|
|
925
|
-
}
|
|
926
|
-
const install = commands.install(manager);
|
|
927
|
-
onProgress(`Running ${manager} install\u2026`);
|
|
928
|
-
const result = await runCommand(install.command, install.args, { cwd: rootDir, timeoutMs: INSTALL_TIMEOUT_MS });
|
|
929
|
-
if (result.exitCode !== 0) {
|
|
930
|
-
const reason = result.timedOut ? `${manager} install timed out` : `${manager} install failed`;
|
|
931
|
-
throw new InstallError(reason, failureDetails(manager, result));
|
|
932
|
-
}
|
|
933
|
-
const lockName = manager === "pnpm" ? "pnpm-lock.yaml" : "package-lock.json";
|
|
934
|
-
onProgress(`Checking workspace links in ${lockName}\u2026`);
|
|
935
|
-
const integrityViolations = verifyAlignedLinks(manager, module, rootDir, updates);
|
|
936
|
-
if (manager === "pnpm") {
|
|
937
|
-
const audit2 = integrityViolations.length > 0 ? void 0 : await runPnpmAudit(rootDir);
|
|
938
|
-
return {
|
|
939
|
-
updatedCount: updates.length,
|
|
940
|
-
auditMessage: audit2?.message ?? "",
|
|
941
|
-
auditSeverity: audit2?.severity ?? "clean",
|
|
942
|
-
fundingMessage: void 0,
|
|
943
|
-
integrityViolations
|
|
944
|
-
};
|
|
945
|
-
}
|
|
946
|
-
const combined = `${result.stdout}
|
|
947
|
-
${result.stderr}`;
|
|
948
|
-
const audit = parseAuditSummary(combined);
|
|
949
|
-
const fundingMatch = combined.match(/(\d+ packages? (?:is|are) looking for funding)/i);
|
|
950
|
-
return {
|
|
951
|
-
updatedCount: updates.length,
|
|
952
|
-
auditMessage: audit.message,
|
|
953
|
-
auditSeverity: audit.severity,
|
|
954
|
-
fundingMessage: fundingMatch ? fundingMatch[0] : void 0,
|
|
955
|
-
integrityViolations
|
|
956
|
-
};
|
|
957
|
-
}
|
|
958
|
-
async resync(_module, rootDir, onProgress) {
|
|
959
|
-
const name = detectPackageManager(rootDir).name;
|
|
960
|
-
if (name !== "npm" && name !== "pnpm") return;
|
|
961
|
-
const restore = commands.restore(name);
|
|
962
|
-
onProgress(`Restoring node_modules (${name} install)\u2026`);
|
|
963
|
-
const result = await runCommand(restore.command, restore.args, { cwd: rootDir, timeoutMs: INSTALL_TIMEOUT_MS });
|
|
964
|
-
if (result.exitCode !== 0) {
|
|
965
|
-
throw new InstallError(
|
|
966
|
-
`${name} install failed while restoring the previous state`,
|
|
967
|
-
name === "pnpm" ? failureDetails(name, result) : tail(result.stderr)
|
|
968
|
-
);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
};
|
|
972
|
-
function writeCatalogRanges(updates) {
|
|
973
|
-
const sources = /* @__PURE__ */ new Map();
|
|
974
|
-
for (const item of updates) {
|
|
975
|
-
const declaration = item.declaration;
|
|
976
|
-
try {
|
|
977
|
-
let source = sources.get(declaration.file) ?? readFileSync7(declaration.file, "utf8");
|
|
978
|
-
source = setCatalogRange(source, declaration.catalogName ?? "default", item.coordinate.artifact, item.newRange);
|
|
979
|
-
sources.set(declaration.file, source);
|
|
980
|
-
} catch (err) {
|
|
981
|
-
throw new InstallError(err instanceof Error ? err.message : `Cannot update ${declaration.displayPath}.`);
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
for (const [file, source] of sources) writeFileSync(file, source, "utf8");
|
|
985
|
-
}
|
|
986
|
-
async function runPnpmAudit(rootDir) {
|
|
987
|
-
try {
|
|
988
|
-
const result = await runCommand("pnpm", ["audit", "--json"], { cwd: rootDir, timeoutMs: AUDIT_TIMEOUT_MS });
|
|
989
|
-
if (result.timedOut) return unavailable(`pnpm audit timed out after ${AUDIT_TIMEOUT_MS / 1e3} seconds`);
|
|
990
|
-
return parsePnpmAudit(result.stdout);
|
|
991
|
-
} catch {
|
|
992
|
-
return unavailable("pnpm audit returned no result");
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
var VULNERABILITY_LINE = /^(?:found\s+)?(\d+)\s+(?:(?:info|low|moderate|high|critical)\s+severity\s+)?vulnerabilit(?:y|ies)\b.*$/i;
|
|
996
|
-
function parseAuditSummary(output) {
|
|
997
|
-
for (const line of output.split(/\r?\n/)) {
|
|
998
|
-
const match = line.trim().match(VULNERABILITY_LINE);
|
|
999
|
-
if (match && Number(match[1]) > 0) return { message: line.trim(), severity: "warn" };
|
|
1000
|
-
}
|
|
1001
|
-
return { message: "No known vulnerabilities found.", severity: "clean" };
|
|
1002
|
-
}
|
|
1003
|
-
function verifyAlignedLinks(manager, module, rootDir, updates) {
|
|
1004
|
-
const aligned = updates.filter((u) => u.origin.kind === "workspace").map((u) => u.coordinate.identifier);
|
|
1005
|
-
if (aligned.length === 0) return [];
|
|
1006
|
-
const moduleRelPath = relative2(rootDir, module.path).replace(/\\/g, "/");
|
|
1007
|
-
if (manager === "pnpm") return findUnlinkedPnpmDependencies(readPnpmLockfile(rootDir), moduleRelPath, aligned);
|
|
1008
|
-
const lockfile = readLockfile(rootDir);
|
|
1009
|
-
if (!lockfile) return ["package-lock.json is missing; workspace links cannot be verified"];
|
|
1010
|
-
return findUnlinkedWorkspaceDependencies(lockfile, moduleRelPath, aligned);
|
|
1011
|
-
}
|
|
1012
|
-
function serializeLike(original, value) {
|
|
1013
|
-
const indent = original.match(/^[ \t]+(?=")/m)?.[0] ?? " ";
|
|
1014
|
-
const newline = original.includes("\r\n") ? "\r\n" : "\n";
|
|
1015
|
-
const body = JSON.stringify(value, null, indent).replace(/\n/g, newline);
|
|
1016
|
-
return original.endsWith("\n") ? body + newline : body;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
// src/adapters/npm/npm-verifier.ts
|
|
1020
|
-
var VERIFY_TIMEOUT_MS = 10 * 6e4;
|
|
1021
|
-
var NpmVerificationAdapter = class {
|
|
1022
|
-
async verify(_module, rootDir, options = {}) {
|
|
1023
|
-
let manager = "npm";
|
|
1024
|
-
if (!options.customScript) {
|
|
1025
|
-
try {
|
|
1026
|
-
manager = await prepareSupportedPackageManager(rootDir);
|
|
1027
|
-
} catch (err) {
|
|
1028
|
-
if (err instanceof UnsupportedPackageManagerError || err instanceof PackageManagerNotFoundError) {
|
|
1029
|
-
return { status: "warn", details: err.message, label: options.customLabel || "Dependency verification" };
|
|
1030
|
-
}
|
|
1031
|
-
throw err;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
const defaultLabel = manager === "pnpm" ? "Lockfile consistency (pnpm install --frozen-lockfile)" : "Dependency tree integrity (npm ls)";
|
|
1035
|
-
const label = options.customLabel || options.customScript || defaultLabel;
|
|
1036
|
-
const verify = commands.verify(manager);
|
|
1037
|
-
const result = options.customScript ? await runShellScript(options.customScript, { cwd: rootDir, timeoutMs: VERIFY_TIMEOUT_MS }) : await runCommand(verify.command, verify.args, { cwd: rootDir, timeoutMs: VERIFY_TIMEOUT_MS });
|
|
1038
|
-
if (result.exitCode === 0) {
|
|
1039
|
-
const details = manager === "pnpm" && !options.customScript ? "pnpm-lock.yaml is up to date with all package.json files." : "All dependencies are installed and consistent.";
|
|
1040
|
-
return { status: "clean", details, label };
|
|
1041
|
-
}
|
|
1042
|
-
const output = manager === "pnpm" && !options.customScript ? tail(`${result.stdout}
|
|
1043
|
-
${result.stderr}`) : tail(result.stderr || result.stdout);
|
|
1044
|
-
return { status: "warn", details: result.timedOut ? "Verification timed out." : output, label };
|
|
1045
|
-
}
|
|
1046
|
-
};
|
|
1047
|
-
|
|
1048
|
-
// src/adapters/npm/npm-strategy.ts
|
|
1049
|
-
var NpmEcosystemStrategy = class {
|
|
1050
|
-
ecosystem = "npm";
|
|
1051
|
-
displayName = "Node / npm";
|
|
1052
|
-
packageManagerFor(rootDir) {
|
|
1053
|
-
return detectPackageManager(rootDir).name;
|
|
1054
|
-
}
|
|
1055
|
-
displayNameFor(rootDir) {
|
|
1056
|
-
return detectPackageManager(rootDir).name === "pnpm" ? "Node / pnpm" : "Node / npm";
|
|
1057
|
-
}
|
|
1058
|
-
icon = "\u{1F4E6}";
|
|
1059
|
-
manifestFile = "package.json";
|
|
1060
|
-
versioning = npmVersionPolicy;
|
|
1061
|
-
discovery = new NpmModuleDiscoveryAdapter();
|
|
1062
|
-
registry = new NpmRegistryAdapter();
|
|
1063
|
-
reader = new NpmDependencyAdapter(this.registry, this.registry.config);
|
|
1064
|
-
updater = new NpmUpdaterAdapter();
|
|
1065
|
-
verifier = new NpmVerificationAdapter();
|
|
1066
|
-
getTabLabels(counts, t) {
|
|
1067
|
-
return {
|
|
1068
|
-
all: t.tabs.all(counts.total),
|
|
1069
|
-
prod: t.tabs.dependencies(counts.prodCount),
|
|
1070
|
-
dev: t.tabs.devDependencies(counts.devCount)
|
|
1071
|
-
};
|
|
1072
|
-
}
|
|
1073
|
-
};
|
|
1074
|
-
|
|
1075
|
-
// src/adapters/cargo/cargo-strategy.ts
|
|
1076
|
-
import semver10 from "semver";
|
|
1077
|
-
|
|
1078
|
-
// src/adapters/cargo/cargo-discovery.ts
|
|
1079
|
-
import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync8 } from "fs";
|
|
1080
|
-
import { dirname as dirname4, join as join9, relative as relative3, resolve as resolve4 } from "path";
|
|
1081
|
-
|
|
1082
|
-
// src/adapters/cargo/cargo-command.ts
|
|
1083
|
-
var CargoUnavailableError = class extends Error {
|
|
1084
|
-
constructor() {
|
|
1085
|
-
super("Cargo is not available: no `cargo` on the PATH. Install Rust (https://rustup.rs).");
|
|
1086
|
-
this.name = "CargoUnavailableError";
|
|
1087
|
-
}
|
|
1088
|
-
};
|
|
1089
|
-
async function runCargo(args, options) {
|
|
1090
|
-
let result;
|
|
1091
|
-
try {
|
|
1092
|
-
result = await runCommand("cargo", args, options);
|
|
1093
|
-
} catch (err) {
|
|
1094
|
-
if (err.code === "ENOENT") throw new CargoUnavailableError();
|
|
1095
|
-
throw err;
|
|
1096
|
-
}
|
|
1097
|
-
if (looksLikeMissingCommand(result) && !await cargoStarts(options.cwd)) throw new CargoUnavailableError();
|
|
1098
|
-
return result;
|
|
1099
|
-
}
|
|
1100
|
-
function looksLikeMissingCommand(result) {
|
|
1101
|
-
return process.platform === "win32" && result.exitCode !== 0 && !result.stdout.trim() && !/^error/m.test(result.stderr);
|
|
1102
|
-
}
|
|
1103
|
-
async function cargoStarts(cwd) {
|
|
1104
|
-
try {
|
|
1105
|
-
return (await runCommand("cargo", ["--version"], { cwd, timeoutMs: 3e4 })).exitCode === 0;
|
|
1106
|
-
} catch {
|
|
1107
|
-
return false;
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
function cargoErrors(output) {
|
|
1111
|
-
const lines = output.split(/\r?\n/);
|
|
1112
|
-
const errors = lines.filter((l) => /^error(\[E\d+\])?:/.test(l));
|
|
1113
|
-
const summary = (errors.length > 0 ? errors : lines.filter((l) => l.trim()).slice(-10)).slice(0, 12);
|
|
1114
|
-
if (/toolchain '.+' is not installed/.test(output)) {
|
|
1115
|
-
summary.push("Install it with `rustup toolchain install` (the project pins it in rust-toolchain.toml).");
|
|
1116
|
-
}
|
|
1117
|
-
return summary.join("\n");
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
// src/adapters/cargo/cargo-metadata.ts
|
|
1121
|
-
var CargoMetadataError = class extends Error {
|
|
1122
|
-
constructor(message2, dir) {
|
|
1123
|
-
super(message2);
|
|
1124
|
-
this.dir = dir;
|
|
1125
|
-
this.name = "CargoMetadataError";
|
|
1126
|
-
}
|
|
1127
|
-
dir;
|
|
1128
|
-
};
|
|
1129
|
-
async function loadMetadata(dir) {
|
|
1130
|
-
const result = await runCargo(["metadata", "--no-deps", "--format-version", "1"], { cwd: dir, timeoutMs: 6e4 });
|
|
1131
|
-
if (result.exitCode !== 0) {
|
|
1132
|
-
throw new CargoMetadataError(`cargo metadata failed in ${dir}: ${cargoErrors(result.stderr || result.stdout)}`, dir);
|
|
1133
|
-
}
|
|
1134
|
-
try {
|
|
1135
|
-
return JSON.parse(result.stdout);
|
|
1136
|
-
} catch {
|
|
1137
|
-
throw new CargoMetadataError(`cargo metadata printed no JSON in ${dir}.`, dir);
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
// src/adapters/cargo/cargo-discovery.ts
|
|
1142
|
-
var IGNORED = /* @__PURE__ */ new Set(["target", "node_modules", "vendor", "dist", "build", "out", "src"]);
|
|
1143
|
-
var norm = (path) => resolve4(path).replace(/\\/g, "/");
|
|
1144
|
-
function findManifests(root) {
|
|
1145
|
-
const found = [];
|
|
1146
|
-
const walk = (dir, depth) => {
|
|
1147
|
-
let entries2;
|
|
1148
|
-
try {
|
|
1149
|
-
entries2 = readdirSync(dir, { withFileTypes: true });
|
|
1150
|
-
} catch {
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
if (entries2.some((e) => e.isFile() && e.name === "Cargo.toml")) found.push(join9(dir, "Cargo.toml"));
|
|
1154
|
-
for (const entry of entries2) {
|
|
1155
|
-
if (entry.isDirectory() && !entry.name.startsWith(".") && !IGNORED.has(entry.name)) walk(join9(dir, entry.name), depth + 1);
|
|
1156
|
-
}
|
|
1157
|
-
};
|
|
1158
|
-
walk(root, 0);
|
|
1159
|
-
return found.sort((a, b) => a.split(/[\\/]/).length - b.split(/[\\/]/).length || a.localeCompare(b));
|
|
1160
|
-
}
|
|
1161
|
-
var declaresWorkspace = (dir) => {
|
|
1162
|
-
try {
|
|
1163
|
-
return /^\s*\[workspace[\].]/m.test(readFileSync8(join9(dir, "Cargo.toml"), "utf8"));
|
|
1164
|
-
} catch {
|
|
1165
|
-
return false;
|
|
1166
|
-
}
|
|
1167
|
-
};
|
|
1168
|
-
var CargoModuleDiscoveryAdapter = class {
|
|
1169
|
-
problems = [];
|
|
1170
|
-
diagnostics() {
|
|
1171
|
-
return [...this.problems];
|
|
1172
|
-
}
|
|
1173
|
-
/** The nearest ancestor declaring `[workspace]`; failing that, the nearest directory with a manifest. */
|
|
1174
|
-
findRoot(startDir, boundaryDir) {
|
|
1175
|
-
const boundary = boundaryDir ? resolve4(boundaryDir) : void 0;
|
|
1176
|
-
const chain = [];
|
|
1177
|
-
for (let dir = resolve4(startDir); ; ) {
|
|
1178
|
-
chain.push(dir);
|
|
1179
|
-
const parent = dirname4(dir);
|
|
1180
|
-
if (dir === boundary || parent === dir) break;
|
|
1181
|
-
dir = parent;
|
|
1182
|
-
}
|
|
1183
|
-
return chain.find(declaresWorkspace) ?? chain.find((d) => existsSync5(join9(d, "Cargo.toml")));
|
|
1184
|
-
}
|
|
1185
|
-
async discover(rootDir) {
|
|
1186
|
-
const covered = /* @__PURE__ */ new Set();
|
|
1187
|
-
const modules = [];
|
|
1188
|
-
const errors = [];
|
|
1189
|
-
this.problems = [];
|
|
1190
|
-
for (const manifest of findManifests(rootDir)) {
|
|
1191
|
-
if (covered.has(norm(manifest))) continue;
|
|
1192
|
-
try {
|
|
1193
|
-
const metadata = await loadMetadata(dirname4(manifest));
|
|
1194
|
-
for (const pkg of metadata.packages) {
|
|
1195
|
-
covered.add(norm(pkg.manifest_path));
|
|
1196
|
-
const path = dirname4(pkg.manifest_path);
|
|
1197
|
-
const relPath = relative3(rootDir, path).replace(/\\/g, "/");
|
|
1198
|
-
modules.push({
|
|
1199
|
-
id: pkg.name,
|
|
1200
|
-
name: pkg.name,
|
|
1201
|
-
path,
|
|
1202
|
-
relPath: relPath === "" ? "Root" : relPath,
|
|
1203
|
-
ecosystem: "cargo",
|
|
1204
|
-
isRoot: relPath === "",
|
|
1205
|
-
version: pkg.version,
|
|
1206
|
-
aggregatorDir: resolve4(metadata.workspace_root)
|
|
1207
|
-
});
|
|
1208
|
-
}
|
|
1209
|
-
} catch (err) {
|
|
1210
|
-
if (err instanceof CargoUnavailableError) throw err;
|
|
1211
|
-
if (err instanceof CargoMetadataError) {
|
|
1212
|
-
errors.push(err);
|
|
1213
|
-
this.problems.push(err.message);
|
|
1214
|
-
} else throw err;
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
if (modules.length === 0 && errors.length > 0) throw errors[0];
|
|
1218
|
-
modules.sort((a, b) => a.isRoot ? -1 : b.isRoot ? 1 : a.relPath.localeCompare(b.relPath));
|
|
1219
|
-
const taken = /* @__PURE__ */ new Set();
|
|
1220
|
-
for (const module of modules) {
|
|
1221
|
-
if (taken.has(module.id)) module.id = `${module.name} (${module.relPath})`;
|
|
1222
|
-
taken.add(module.id);
|
|
1223
|
-
}
|
|
1224
|
-
return modules;
|
|
1225
|
-
}
|
|
1226
|
-
};
|
|
1227
|
-
|
|
1228
|
-
// src/adapters/cargo/cargo-config.ts
|
|
1229
|
-
import { existsSync as existsSync6, readFileSync as readFileSync9 } from "fs";
|
|
1230
|
-
import { homedir } from "os";
|
|
1231
|
-
import { dirname as dirname5, isAbsolute, join as join10, resolve as resolve5 } from "path";
|
|
1232
|
-
import { getStaticTOMLValue, parseTOML } from "toml-eslint-parser";
|
|
1233
|
-
var CRATES_IO = "registry+https://github.com/rust-lang/crates.io-index";
|
|
1234
|
-
var PUBLIC_HOSTS = /* @__PURE__ */ new Set(["index.crates.io", "static.crates.io", "crates.io", "github.com"]);
|
|
1235
|
-
var UnsupportedSourceError = class extends Error {
|
|
1236
|
-
constructor(detail) {
|
|
1237
|
-
super(`Cargo is configured to use a package source Elevate cannot read (${detail}). No lookup was made.`);
|
|
1238
|
-
this.name = "UnsupportedSourceError";
|
|
1239
|
-
}
|
|
1240
|
-
};
|
|
1241
|
-
function isPublicIndex(index) {
|
|
1242
|
-
try {
|
|
1243
|
-
const url = new URL(index.replace(/^(sparse|registry)\+/, ""));
|
|
1244
|
-
return PUBLIC_HOSTS.has(url.hostname.toLowerCase()) && (url.hostname !== "github.com" || url.pathname.includes("crates.io-index"));
|
|
1245
|
-
} catch {
|
|
1246
|
-
return false;
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
function merge(base, top) {
|
|
1250
|
-
const out = { ...base };
|
|
1251
|
-
for (const [key, value] of Object.entries(top)) {
|
|
1252
|
-
const existing = out[key];
|
|
1253
|
-
out[key] = value && typeof value === "object" && !Array.isArray(value) && existing && typeof existing === "object" && !Array.isArray(existing) ? merge(existing, value) : value;
|
|
1254
|
-
}
|
|
1255
|
-
return out;
|
|
1256
|
-
}
|
|
1257
|
-
function loadFile(file, depth = 0) {
|
|
1258
|
-
let table;
|
|
1259
|
-
try {
|
|
1260
|
-
table = getStaticTOMLValue(parseTOML(readFileSync9(file, "utf8")));
|
|
1261
|
-
} catch {
|
|
1262
|
-
return {};
|
|
1263
|
-
}
|
|
1264
|
-
const includes = table.include;
|
|
1265
|
-
delete table.include;
|
|
1266
|
-
if (!Array.isArray(includes) || depth > 4) return table;
|
|
1267
|
-
let included = {};
|
|
1268
|
-
for (const entry of includes) {
|
|
1269
|
-
const path = typeof entry === "string" ? entry : entry?.path;
|
|
1270
|
-
if (typeof path === "string") included = merge(included, loadFile(isAbsolute(path) ? path : resolve5(dirname5(file), path), depth + 1));
|
|
1271
|
-
}
|
|
1272
|
-
return merge(included, table);
|
|
1273
|
-
}
|
|
1274
|
-
var CargoConfigReader = class {
|
|
1275
|
-
env;
|
|
1276
|
-
cache = /* @__PURE__ */ new Map();
|
|
1277
|
-
constructor(options = {}) {
|
|
1278
|
-
this.env = options.env ?? process.env;
|
|
1279
|
-
}
|
|
1280
|
-
/** The configuration as Cargo would merge it for `cwd`: nearest file first, CARGO_HOME last. */
|
|
1281
|
-
config(cwd) {
|
|
1282
|
-
const start = resolve5(cwd);
|
|
1283
|
-
let cached = this.cache.get(start);
|
|
1284
|
-
if (cached) return cached;
|
|
1285
|
-
const files = [];
|
|
1286
|
-
for (let dir = start; ; ) {
|
|
1287
|
-
for (const name of ["config.toml", "config"]) {
|
|
1288
|
-
const file = join10(dir, ".cargo", name);
|
|
1289
|
-
if (existsSync6(file)) {
|
|
1290
|
-
files.push(file);
|
|
1291
|
-
break;
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
const parent = dirname5(dir);
|
|
1295
|
-
if (parent === dir) break;
|
|
1296
|
-
dir = parent;
|
|
1297
|
-
}
|
|
1298
|
-
const home2 = this.env.CARGO_HOME ?? join10(homedir(), ".cargo");
|
|
1299
|
-
for (const name of ["config.toml", "config"]) {
|
|
1300
|
-
const file = join10(home2, name);
|
|
1301
|
-
if (existsSync6(file) && !files.includes(file)) {
|
|
1302
|
-
files.push(file);
|
|
1303
|
-
break;
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
cached = files.reduceRight((acc, file) => merge(acc, loadFile(file)), {});
|
|
1307
|
-
this.cache.set(start, cached);
|
|
1308
|
-
return cached;
|
|
1309
|
-
}
|
|
1310
|
-
/** `[source.*]` comes from files only: Cargo ignores `CARGO_SOURCE_*` variables (checked with Cargo 1.97). */
|
|
1311
|
-
sourceField(config, source, field) {
|
|
1312
|
-
const value = config.source?.[source]?.[field];
|
|
1313
|
-
return typeof value === "string" ? value : void 0;
|
|
1314
|
-
}
|
|
1315
|
-
/**
|
|
1316
|
-
* The index a lookup goes to. `registry` is the index URL of an alternative
|
|
1317
|
-
* registry as Cargo reports it, or undefined for crates.io.
|
|
1318
|
-
*/
|
|
1319
|
-
async resolve(registry, cwd) {
|
|
1320
|
-
const config = this.config(cwd);
|
|
1321
|
-
let name = registry === void 0 ? "crates-io" : this.sourceNamed(config, registry);
|
|
1322
|
-
let index = registry ?? CRATES_IO;
|
|
1323
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1324
|
-
while (name !== void 0) {
|
|
1325
|
-
if (seen.has(name)) throw new UnsupportedSourceError(`replacement cycle at '${name}'`);
|
|
1326
|
-
seen.add(name);
|
|
1327
|
-
const next = this.sourceField(config, name, "replace-with");
|
|
1328
|
-
if (next === void 0) break;
|
|
1329
|
-
for (const kind of ["directory", "local-registry", "git"]) {
|
|
1330
|
-
if (this.sourceField(config, next, kind) !== void 0) throw new UnsupportedSourceError(`'${next}' is a ${kind} source`);
|
|
1331
|
-
}
|
|
1332
|
-
const target = this.sourceField(config, next, "registry");
|
|
1333
|
-
if (target !== void 0) {
|
|
1334
|
-
index = target;
|
|
1335
|
-
name = next;
|
|
1336
|
-
if (this.sourceField(config, next, "replace-with") === void 0) break;
|
|
1337
|
-
continue;
|
|
1338
|
-
}
|
|
1339
|
-
if (this.sourceField(config, next, "replace-with") === void 0) {
|
|
1340
|
-
throw new UnsupportedSourceError(`'${next}' has no registry`);
|
|
1341
|
-
}
|
|
1342
|
-
name = next;
|
|
1343
|
-
}
|
|
1344
|
-
return { index, isPublic: isPublicIndex(index) };
|
|
1345
|
-
}
|
|
1346
|
-
/** Name of the `[source.*]` table whose registry is `url`, if any. */
|
|
1347
|
-
sourceNamed(config, url) {
|
|
1348
|
-
return Object.keys(config.source ?? {}).find((n) => this.sourceField(config, n, "registry") === url);
|
|
1349
|
-
}
|
|
1350
|
-
};
|
|
1351
|
-
|
|
1352
|
-
// src/version.ts
|
|
1353
|
-
import { createRequire } from "module";
|
|
1354
|
-
var VERSION = true ? "1.6.0" : fromPackage();
|
|
1355
|
-
|
|
1356
|
-
// src/adapters/cargo/cargo-index.ts
|
|
1357
|
-
var RegistryAuthRequiredError = class extends Error {
|
|
1358
|
-
constructor(index) {
|
|
1359
|
-
super(`The registry ${index} requires authentication. Elevate does not handle registry credentials.`);
|
|
1360
|
-
this.index = index;
|
|
1361
|
-
this.name = "RegistryAuthRequiredError";
|
|
1362
|
-
}
|
|
1363
|
-
index;
|
|
1364
|
-
};
|
|
1365
|
-
var CrateNotFoundError = class extends Error {
|
|
1366
|
-
constructor(crate, index) {
|
|
1367
|
-
super(`'${crate}' is not in the registry ${index}.`);
|
|
1368
|
-
this.crate = crate;
|
|
1369
|
-
this.index = index;
|
|
1370
|
-
this.name = "CrateNotFoundError";
|
|
1371
|
-
}
|
|
1372
|
-
crate;
|
|
1373
|
-
index;
|
|
1374
|
-
};
|
|
1375
|
-
var UnsupportedIndexError = class extends Error {
|
|
1376
|
-
constructor(index) {
|
|
1377
|
-
super(`The registry index ${index} is not a sparse index; Elevate cannot read it.`);
|
|
1378
|
-
this.index = index;
|
|
1379
|
-
this.name = "UnsupportedIndexError";
|
|
1380
|
-
}
|
|
1381
|
-
index;
|
|
1382
|
-
};
|
|
1383
|
-
var CRATES_IO_SPARSE = "https://index.crates.io/";
|
|
1384
|
-
var CRATES_IO_GIT = "registry+https://github.com/rust-lang/crates.io-index";
|
|
1385
|
-
function indexPath(name) {
|
|
1386
|
-
const n = name.toLowerCase();
|
|
1387
|
-
if (n.length <= 2) return `${n.length}/${n}`;
|
|
1388
|
-
if (n.length === 3) return `3/${n[0]}/${n}`;
|
|
1389
|
-
return `${n.slice(0, 2)}/${n.slice(2, 4)}/${n}`;
|
|
1390
|
-
}
|
|
1391
|
-
function parseIndex(text) {
|
|
1392
|
-
const versions = [];
|
|
1393
|
-
for (const raw of text.split(/\r?\n/)) {
|
|
1394
|
-
if (!raw.trim()) continue;
|
|
1395
|
-
try {
|
|
1396
|
-
const entry = JSON.parse(raw);
|
|
1397
|
-
if (typeof entry.vers !== "string") continue;
|
|
1398
|
-
versions.push({ version: entry.vers, yanked: entry.yanked === true, rustVersion: entry.rust_version });
|
|
1399
|
-
} catch {
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
return versions;
|
|
1403
|
-
}
|
|
1404
|
-
function sparseRoot(index) {
|
|
1405
|
-
if (index === CRATES_IO_GIT) return CRATES_IO_SPARSE;
|
|
1406
|
-
if (index.startsWith("sparse+")) {
|
|
1407
|
-
const url = index.slice("sparse+".length);
|
|
1408
|
-
return url.endsWith("/") ? url : `${url}/`;
|
|
1409
|
-
}
|
|
1410
|
-
throw new UnsupportedIndexError(index);
|
|
1411
|
-
}
|
|
1412
|
-
var userAgent = () => `elevate/${VERSION} (+https://github.com/DennisRepa/Elevate)`;
|
|
1413
|
-
function networkReason(err) {
|
|
1414
|
-
const cause = err?.cause?.message;
|
|
1415
|
-
return cause ?? (err instanceof Error ? err.message : String(err));
|
|
1416
|
-
}
|
|
1417
|
-
var CargoIndexClient = class {
|
|
1418
|
-
constructor(fetchFn = fetch, env = process.env) {
|
|
1419
|
-
this.fetchFn = fetchFn;
|
|
1420
|
-
this.env = env;
|
|
1421
|
-
}
|
|
1422
|
-
fetchFn;
|
|
1423
|
-
env;
|
|
1424
|
-
memo = /* @__PURE__ */ new Map();
|
|
1425
|
-
/** All versions of a crate in oldest-first file order. Memoised per index and crate. */
|
|
1426
|
-
versions(index, crate) {
|
|
1427
|
-
const key = `${index}
|
|
1428
|
-
${crate.toLowerCase()}`;
|
|
1429
|
-
let pending = this.memo.get(key);
|
|
1430
|
-
if (!pending) {
|
|
1431
|
-
pending = this.load(index, crate);
|
|
1432
|
-
this.memo.set(key, pending);
|
|
1433
|
-
}
|
|
1434
|
-
return pending;
|
|
1435
|
-
}
|
|
1436
|
-
async load(index, crate) {
|
|
1437
|
-
const url = `${sparseRoot(index)}${indexPath(crate)}`;
|
|
1438
|
-
let response;
|
|
1439
|
-
try {
|
|
1440
|
-
response = await this.fetchFn(url, { headers: { "user-agent": userAgent() } });
|
|
1441
|
-
} catch (err) {
|
|
1442
|
-
throw new Error(`Cannot reach ${url}: ${networkReason(err)}${this.proxyHint()}`);
|
|
1443
|
-
}
|
|
1444
|
-
if (response.status === 401 || response.status === 403) throw new RegistryAuthRequiredError(index);
|
|
1445
|
-
if (response.status === 404) throw new CrateNotFoundError(crate, index);
|
|
1446
|
-
if (!response.ok) throw new Error(`Registry answered ${response.status} for ${url}.`);
|
|
1447
|
-
return parseIndex(await response.text());
|
|
1448
|
-
}
|
|
1449
|
-
/**
|
|
1450
|
-
* Node's fetch ignores HTTP(S)_PROXY unless NODE_USE_ENV_PROXY=1 is set when
|
|
1451
|
-
* the process starts (measured with Node 22.23; it cannot be switched on later).
|
|
1452
|
-
*/
|
|
1453
|
-
proxyHint() {
|
|
1454
|
-
const proxied = this.env.HTTPS_PROXY || this.env.https_proxy || this.env.HTTP_PROXY || this.env.http_proxy;
|
|
1455
|
-
if (!proxied || this.env.NODE_USE_ENV_PROXY === "1") return "";
|
|
1456
|
-
return " A proxy is configured, but Node does not use it for registry lookups unless you start Elevate with NODE_USE_ENV_PROXY=1.";
|
|
1457
|
-
}
|
|
1458
|
-
};
|
|
1459
|
-
|
|
1460
|
-
// src/adapters/cargo/cargo-registry.ts
|
|
1461
|
-
import semver6 from "semver";
|
|
1462
|
-
|
|
1463
|
-
// src/adapters/cargo/cargo-versions.ts
|
|
1464
|
-
import semver5 from "semver";
|
|
1465
|
-
var rust = (v) => semver5.coerce(v)?.version;
|
|
1466
|
-
function eligibleVersions(versions, options) {
|
|
1467
|
-
const project = options.rustVersion ? rust(options.rustVersion) : void 0;
|
|
1468
|
-
return versions.filter((v) => !v.yanked && semver5.valid(v.version)).filter((v) => options.channel === "all" || semver5.prerelease(v.version) === null).filter((v) => {
|
|
1469
|
-
const needs = v.rustVersion ? rust(v.rustVersion) : void 0;
|
|
1470
|
-
return !(project && needs && semver5.gt(needs, project));
|
|
1471
|
-
}).map((v) => v.version).sort(semver5.rcompare);
|
|
1472
|
-
}
|
|
1473
|
-
function selectVersion(versions, options) {
|
|
1474
|
-
const candidates = versions.filter((v) => !v.yanked && semver5.valid(v.version)).filter((v) => options.channel === "all" || semver5.prerelease(v.version) === null).sort((a, b) => semver5.rcompare(a.version, b.version));
|
|
1475
|
-
const project = options.rustVersion ? rust(options.rustVersion) : void 0;
|
|
1476
|
-
let blocked;
|
|
1477
|
-
for (const candidate of candidates) {
|
|
1478
|
-
const needs = candidate.rustVersion ? rust(candidate.rustVersion) : void 0;
|
|
1479
|
-
if (project && needs && semver5.gt(needs, project)) {
|
|
1480
|
-
blocked ??= { version: candidate.version, requires: candidate.rustVersion };
|
|
1481
|
-
continue;
|
|
1482
|
-
}
|
|
1483
|
-
return { version: candidate.version, ...blocked ? { blockedByMsrv: blocked } : {} };
|
|
1484
|
-
}
|
|
1485
|
-
return { version: void 0, ...blocked ? { blockedByMsrv: blocked } : {} };
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
// src/adapters/cargo/cargo-registry.ts
|
|
1489
|
-
var CargoRegistryAdapter = class {
|
|
1490
|
-
constructor(deps) {
|
|
1491
|
-
this.deps = deps;
|
|
1492
|
-
}
|
|
1493
|
-
deps;
|
|
1494
|
-
async resolveEndpoint(coordinate, cwd) {
|
|
1495
|
-
const { index, isPublic } = await this.deps.endpoint(coordinate.registry, cwd);
|
|
1496
|
-
return { url: index, isPublic };
|
|
1497
|
-
}
|
|
1498
|
-
async getLatestVersion(coordinate, channel = "stable", cwd = process.cwd()) {
|
|
1499
|
-
return selectVersion(await this.load(coordinate, cwd), { channel }).version ?? null;
|
|
1500
|
-
}
|
|
1501
|
-
/** Published, not yanked, newest first. Lookup errors propagate so they are not mistaken for "no versions". */
|
|
1502
|
-
async getAllVersions(coordinate, cwd = process.cwd()) {
|
|
1503
|
-
return (await this.load(coordinate, cwd)).filter((v) => !v.yanked && semver6.valid(v.version)).map((v) => v.version).sort(semver6.rcompare);
|
|
1504
|
-
}
|
|
1505
|
-
async load(coordinate, cwd) {
|
|
1506
|
-
const { index } = await this.deps.endpoint(coordinate.registry, cwd);
|
|
1507
|
-
return this.deps.index.versions(index, coordinate.artifact);
|
|
1508
|
-
}
|
|
1509
|
-
};
|
|
1510
|
-
|
|
1511
|
-
// src/adapters/cargo/cargo-requirement.ts
|
|
1512
|
-
import semver7 from "semver";
|
|
1513
|
-
var SIMPLE = /^(\^|~|=)?\s*(\d+(?:\.\d+){0,2})$/;
|
|
1514
|
-
function toSemverRange(requirement) {
|
|
1515
|
-
const parts = requirement.split(",").map((p) => p.trim());
|
|
1516
|
-
if (parts.some((p) => p === "")) return null;
|
|
1517
|
-
const converted = parts.map((part) => {
|
|
1518
|
-
const bare = part.match(/^\d/);
|
|
1519
|
-
return bare ? `^${part}` : part;
|
|
1520
|
-
});
|
|
1521
|
-
return semver7.validRange(converted.join(" ")) ? converted.join(" ") : null;
|
|
1522
|
-
}
|
|
1523
|
-
function satisfiesRequirement(requirement, version) {
|
|
1524
|
-
const range = toSemverRange(requirement);
|
|
1525
|
-
return range !== null && semver7.satisfies(version, range, { includePrerelease: false });
|
|
1526
|
-
}
|
|
1527
|
-
function isUpgradableRequirement(requirement) {
|
|
1528
|
-
return SIMPLE.test(requirement.trim());
|
|
1529
|
-
}
|
|
1530
|
-
function requirementFloor(requirement) {
|
|
1531
|
-
const range = toSemverRange(requirement);
|
|
1532
|
-
return range ? semver7.minVersion(range)?.version ?? null : null;
|
|
1533
|
-
}
|
|
1534
|
-
function rewriteRequirement(current, target) {
|
|
1535
|
-
const match = current.trim().match(SIMPLE);
|
|
1536
|
-
if (!match) return target;
|
|
1537
|
-
if (semver7.prerelease(target)) return `${match[1] ?? ""}${target}`;
|
|
1538
|
-
const precision = match[2].split(".").length;
|
|
1539
|
-
const [major, minor, patch] = target.split(/[-+]/)[0].split(".");
|
|
1540
|
-
const written = [major, minor, patch].slice(0, precision).join(".");
|
|
1541
|
-
return `${match[1] ?? ""}${written}`;
|
|
1542
|
-
}
|
|
1543
|
-
function cargoDiff(from, to) {
|
|
1544
|
-
const a = semver7.parse(from);
|
|
1545
|
-
const b = semver7.parse(to);
|
|
1546
|
-
if (!a || !b) return "minor";
|
|
1547
|
-
if (a.major > 0 || b.major > a.major) {
|
|
1548
|
-
if (b.major !== a.major) return "major";
|
|
1549
|
-
return b.minor !== a.minor ? "minor" : "patch";
|
|
1550
|
-
}
|
|
1551
|
-
if (a.minor > 0) {
|
|
1552
|
-
if (b.minor !== a.minor) return "major";
|
|
1553
|
-
return "minor";
|
|
1554
|
-
}
|
|
1555
|
-
return "major";
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
// src/adapters/cargo/cargo-scanner.ts
|
|
1559
|
-
import { readFileSync as readFileSync11 } from "fs";
|
|
1560
|
-
import { join as join11, relative as relative4 } from "path";
|
|
1561
|
-
import semver8 from "semver";
|
|
1562
|
-
|
|
1563
|
-
// src/adapters/cargo/cargo-toml.ts
|
|
1564
|
-
import { parseTOML as parseTOML2 } from "toml-eslint-parser";
|
|
1565
|
-
var SECTIONS2 = {
|
|
1566
|
-
dependencies: "dependencies",
|
|
1567
|
-
"dev-dependencies": "dev-dependencies",
|
|
1568
|
-
dev_dependencies: "dev-dependencies",
|
|
1569
|
-
"build-dependencies": "build-dependencies",
|
|
1570
|
-
build_dependencies: "build-dependencies"
|
|
1571
|
-
};
|
|
1572
|
-
function parseCargoManifest(source) {
|
|
1573
|
-
const root = parseTOML2(source).body[0];
|
|
1574
|
-
const leaves = [];
|
|
1575
|
-
const keyPath = (key) => key.keys.map((k) => k.type === "TOMLBare" ? k.name : k.value);
|
|
1576
|
-
const visitPair = (prefix, pair) => {
|
|
1577
|
-
const path = [...prefix, ...keyPath(pair.key)];
|
|
1578
|
-
leaves.push({ path, node: pair.value });
|
|
1579
|
-
if (pair.value.type === "TOMLInlineTable") for (const inner of pair.value.body) visitPair(path, inner);
|
|
1580
|
-
};
|
|
1581
|
-
for (const element of root.body) {
|
|
1582
|
-
if (element.type === "TOMLKeyValue") visitPair([], element);
|
|
1583
|
-
else if (element.type === "TOMLTable") {
|
|
1584
|
-
const header = keyPath(element.key);
|
|
1585
|
-
for (const pair of element.body) visitPair(header, pair);
|
|
1586
|
-
leaves.push({ path: header, node: element });
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
1590
|
-
for (const { path, node } of leaves) {
|
|
1591
|
-
const located = locateSection(path);
|
|
1592
|
-
if (!located || located.rest.length === 0) continue;
|
|
1593
|
-
const [key, field] = located.rest;
|
|
1594
|
-
const id = `${located.inWorkspace}|${located.target ?? ""}|${located.section}|${key}`;
|
|
1595
|
-
let decl = byKey.get(id);
|
|
1596
|
-
if (!decl) {
|
|
1597
|
-
decl = { key, section: located.section, target: located.target, inWorkspace: located.inWorkspace, inherited: false };
|
|
1598
|
-
byKey.set(id, decl);
|
|
1599
|
-
}
|
|
1600
|
-
if (located.rest.length === 1 && node.type === "TOMLValue" && node.kind === "string") {
|
|
1601
|
-
decl.version = literalOf(node);
|
|
1602
|
-
} else if (field === "version" && located.rest.length === 2 && node.type === "TOMLValue" && node.kind === "string") {
|
|
1603
|
-
decl.version = literalOf(node);
|
|
1604
|
-
} else if (field === "workspace" && located.rest.length === 2 && node.value === true) {
|
|
1605
|
-
decl.inherited = true;
|
|
1606
|
-
} else if (field === "package" && located.rest.length === 2 && node.type === "TOMLValue") {
|
|
1607
|
-
decl.package = String(node.value);
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
return [...byKey.values()];
|
|
1611
|
-
}
|
|
1612
|
-
function literalOf(node) {
|
|
1613
|
-
return { value: node.value, start: node.range[0] + 1, end: node.range[1] - 1 };
|
|
1614
|
-
}
|
|
1615
|
-
function locateSection(path) {
|
|
1616
|
-
let i = 0;
|
|
1617
|
-
let target;
|
|
1618
|
-
let inWorkspace = false;
|
|
1619
|
-
if (path[0] === "workspace" && path[1] === "dependencies") {
|
|
1620
|
-
return { section: "dependencies", inWorkspace: true, target, rest: path.slice(2) };
|
|
1621
|
-
}
|
|
1622
|
-
if (path[0] === "target" && path.length >= 3) {
|
|
1623
|
-
target = path[1];
|
|
1624
|
-
i = 2;
|
|
1625
|
-
}
|
|
1626
|
-
const section = SECTIONS2[path[i] ?? ""];
|
|
1627
|
-
if (!section) return void 0;
|
|
1628
|
-
return { section, inWorkspace, target, rest: path.slice(i + 1) };
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
// src/adapters/cargo/cargo-lockfile.ts
|
|
1632
|
-
import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
|
|
1633
|
-
import { getStaticTOMLValue as getStaticTOMLValue2, parseTOML as parseTOML3 } from "toml-eslint-parser";
|
|
1634
|
-
function readLockedVersions(lockFile) {
|
|
1635
|
-
const locked = /* @__PURE__ */ new Map();
|
|
1636
|
-
if (!existsSync7(lockFile)) return locked;
|
|
1637
|
-
try {
|
|
1638
|
-
const data = getStaticTOMLValue2(parseTOML3(readFileSync10(lockFile, "utf8")));
|
|
1639
|
-
for (const pkg of data.package ?? []) locked.set(pkg.name, [...locked.get(pkg.name) ?? [], pkg.version]);
|
|
1640
|
-
} catch {
|
|
1641
|
-
}
|
|
1642
|
-
return locked;
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
// src/adapters/cargo/cargo-scanner.ts
|
|
1646
|
-
var CONCURRENCY2 = 6;
|
|
1647
|
-
var DIFF_ORDER2 = { major: 0, minor: 1, patch: 2 };
|
|
1648
|
-
var CargoDependencyAdapter = class {
|
|
1649
|
-
constructor(deps) {
|
|
1650
|
-
this.deps = deps;
|
|
1651
|
-
}
|
|
1652
|
-
deps;
|
|
1653
|
-
async scan(module, context) {
|
|
1654
|
-
const metadata = await this.deps.loadMetadata(module.aggregatorDir ?? module.path);
|
|
1655
|
-
const pkg = metadata.packages.find((p) => samePath(join11(p.manifest_path, ".."), module.path));
|
|
1656
|
-
if (!pkg) return { candidates: [], skipped: [] };
|
|
1657
|
-
const own = readDeclarations(pkg.manifest_path);
|
|
1658
|
-
const locks = readLockedVersions(join11(metadata.workspace_root, "Cargo.lock"));
|
|
1659
|
-
const rootManifest = join11(metadata.workspace_root, "Cargo.toml");
|
|
1660
|
-
const root = readDeclarations(rootManifest);
|
|
1661
|
-
const groups = groupDependencies(pkg.dependencies);
|
|
1662
|
-
const outcomes = await mapPooled(groups, CONCURRENCY2, async (group) => {
|
|
1663
|
-
const primary = group.find((d) => d.kind === null) ?? group[0];
|
|
1664
|
-
const key = primary.rename ?? primary.name;
|
|
1665
|
-
const decl = findDeclaration(own, key, primary);
|
|
1666
|
-
const located = locate(decl, root, key, context.rootDir, pkg.manifest_path, rootManifest);
|
|
1667
|
-
const scope = group.every((d) => d.kind === "dev") ? "dev" : "prod";
|
|
1668
|
-
const qualifier = primary.target ?? (primary.kind === "build" ? "build" : void 0);
|
|
1669
|
-
const common = { scope, qualifier };
|
|
1670
|
-
if (primary.source === null && primary.path) {
|
|
1671
|
-
const target = [...context.workspace.values()].find((m) => samePath(m.path, primary.path));
|
|
1672
|
-
return target ? alignment(primary, target, located, common) : null;
|
|
1673
|
-
}
|
|
1674
|
-
if (!primary.source || primary.source.startsWith("git+")) return null;
|
|
1675
|
-
const outcome = await this.registryOutcome(primary, located, pkg, metadata, locks, common, context);
|
|
1676
|
-
const other = group.find((d) => d !== primary && !sameRequirement(d.req, primary.req));
|
|
1677
|
-
if (outcome && "candidate" in outcome && other) {
|
|
1678
|
-
outcome.note = {
|
|
1679
|
-
identifier: primary.name,
|
|
1680
|
-
origin: outcome.candidate.origin.kind,
|
|
1681
|
-
reason: "declaration-mismatch",
|
|
1682
|
-
detail: `${sectionOf(other)} requires ${other.req}; it is not changed`
|
|
1683
|
-
};
|
|
1684
|
-
}
|
|
1685
|
-
return outcome;
|
|
1686
|
-
});
|
|
1687
|
-
const candidates = [];
|
|
1688
|
-
const skipped = [];
|
|
1689
|
-
for (const outcome of outcomes) {
|
|
1690
|
-
if (!outcome) continue;
|
|
1691
|
-
if ("candidate" in outcome) {
|
|
1692
|
-
candidates.push(outcome.candidate);
|
|
1693
|
-
if (outcome.note) skipped.push(outcome.note);
|
|
1694
|
-
} else skipped.push(outcome.skipped);
|
|
1695
|
-
}
|
|
1696
|
-
candidates.sort((a, b) => (DIFF_ORDER2[a.diff] ?? 3) - (DIFF_ORDER2[b.diff] ?? 3));
|
|
1697
|
-
return { candidates, skipped };
|
|
1698
|
-
}
|
|
1699
|
-
async registryOutcome(dep, located, pkg, metadata, locks, common, context) {
|
|
1700
|
-
const literal = located?.version.value ?? dep.req;
|
|
1701
|
-
if (!isUpgradableRequirement(literal)) return null;
|
|
1702
|
-
if (!located || !sameRequirement(located.version.value, dep.req)) {
|
|
1703
|
-
return { skipped: { identifier: dep.name, origin: "public", reason: "declaration-mismatch", detail: `${literal} in the manifest, ${dep.req} for Cargo` } };
|
|
1704
|
-
}
|
|
1705
|
-
let origin = { kind: "public" };
|
|
1706
|
-
if (dep.registry) origin = { kind: "private", matchedBy: "scoped-registry", registry: dep.registry };
|
|
1707
|
-
else if (matchesInternalScope(dep.name, "cargo", context.internalScopes)) origin = { kind: "private", matchedBy: "internal-scope" };
|
|
1708
|
-
const endpoint = await this.deps.endpoint(dep.registry ?? void 0, context.rootDir);
|
|
1709
|
-
if (origin.kind === "private" && endpoint.isPublic) {
|
|
1710
|
-
return { skipped: { identifier: dep.name, origin: "private", reason: "private-on-public-registry", detail: endpoint.index } };
|
|
1711
|
-
}
|
|
1712
|
-
let versions;
|
|
1713
|
-
try {
|
|
1714
|
-
versions = await this.deps.index.versions(endpoint.index, dep.name);
|
|
1715
|
-
} catch (err) {
|
|
1716
|
-
return { skipped: { identifier: dep.name, origin: origin.kind, reason: "lookup-failed", detail: err instanceof Error ? err.message : String(err) } };
|
|
1717
|
-
}
|
|
1718
|
-
const chosen = selectVersion(versions, { channel: context.channel, rustVersion: pkg.rust_version ?? void 0 });
|
|
1719
|
-
const floor = requirementFloor(literal);
|
|
1720
|
-
if (!chosen.version) {
|
|
1721
|
-
if (chosen.blockedByMsrv && floor) {
|
|
1722
|
-
const { version, requires } = chosen.blockedByMsrv;
|
|
1723
|
-
return { skipped: { identifier: dep.name, origin: origin.kind, reason: "incompatible-rust-version", detail: ` (${version} needs Rust ${requires})` } };
|
|
1724
|
-
}
|
|
1725
|
-
return { skipped: { identifier: dep.name, origin: origin.kind, reason: "lookup-failed", detail: "no usable version in the registry" } };
|
|
1726
|
-
}
|
|
1727
|
-
const latest = chosen.version;
|
|
1728
|
-
if (!floor || !semver8.gt(latest, floor)) {
|
|
1729
|
-
const held = chosen.blockedByMsrv;
|
|
1730
|
-
if (floor && held && semver8.gt(held.version, floor)) {
|
|
1731
|
-
return { skipped: { identifier: dep.name, origin: origin.kind, reason: "incompatible-rust-version", detail: ` (${held.version} needs Rust ${held.requires})` } };
|
|
1732
|
-
}
|
|
1733
|
-
return null;
|
|
1734
|
-
}
|
|
1735
|
-
const newRange = rewriteRequirement(literal, latest);
|
|
1736
|
-
if (newRange === literal) return lockOutcome(dep, literal, origin, versions, pkg, metadata, locks, common, context);
|
|
1737
|
-
const tag = semver8.prerelease(latest) ? extractPreReleaseTag(latest) ?? "PRE" : void 0;
|
|
1738
|
-
const diff = cargoDiff(floor, latest);
|
|
1739
|
-
const candidate = {
|
|
1740
|
-
coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "cargo", ...dep.registry ? { registry: dep.registry } : {} },
|
|
1741
|
-
currentRange: literal,
|
|
1742
|
-
currentClean: floor,
|
|
1743
|
-
latest,
|
|
1744
|
-
newRange,
|
|
1745
|
-
diff,
|
|
1746
|
-
scope: common.scope,
|
|
1747
|
-
selected: diff !== "major",
|
|
1748
|
-
origin,
|
|
1749
|
-
action: "update",
|
|
1750
|
-
declaration: located.declaration,
|
|
1751
|
-
isPreRelease: tag !== void 0,
|
|
1752
|
-
preReleaseTag: tag,
|
|
1753
|
-
...common.qualifier ? { qualifier: common.qualifier } : {},
|
|
1754
|
-
...located.inherited ? { sharedWith: sharersOf(metadata, pkg, dep.rename ?? dep.name) } : {}
|
|
1755
|
-
};
|
|
1756
|
-
return { candidate };
|
|
1757
|
-
}
|
|
1758
|
-
};
|
|
1759
|
-
function lockOutcome(dep, requirement, origin, versions, pkg, metadata, locks, common, context) {
|
|
1760
|
-
const locked = (locks.get(dep.name) ?? []).filter((v) => satisfiesRequirement(requirement, v)).sort(semver8.rcompare)[0];
|
|
1761
|
-
if (!locked) return null;
|
|
1762
|
-
const options = { channel: context.channel, rustVersion: pkg.rust_version ?? void 0 };
|
|
1763
|
-
const allowed = versions.filter((v) => satisfiesRequirement(requirement, v.version));
|
|
1764
|
-
const newer = eligibleVersions(allowed, options).filter((v) => semver8.gt(v, locked));
|
|
1765
|
-
const latest = newer[0];
|
|
1766
|
-
if (!latest) return null;
|
|
1767
|
-
const lockFile = join11(metadata.workspace_root, "Cargo.lock");
|
|
1768
|
-
return {
|
|
1769
|
-
candidate: {
|
|
1770
|
-
coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "cargo", ...dep.registry ? { registry: dep.registry } : {} },
|
|
1771
|
-
currentRange: locked,
|
|
1772
|
-
currentClean: locked,
|
|
1773
|
-
latest,
|
|
1774
|
-
newRange: latest,
|
|
1775
|
-
diff: cargoDiff(locked, latest),
|
|
1776
|
-
scope: common.scope,
|
|
1777
|
-
// A release the requirement allows is compatible by definition, so it is safe to preselect.
|
|
1778
|
-
selected: true,
|
|
1779
|
-
origin,
|
|
1780
|
-
action: "lock",
|
|
1781
|
-
declaration: { file: lockFile, kind: "lockfile", displayPath: display(context.rootDir, lockFile) },
|
|
1782
|
-
availableVersions: newer,
|
|
1783
|
-
...common.qualifier ? { qualifier: common.qualifier } : {}
|
|
1784
|
-
}
|
|
1785
|
-
};
|
|
1786
|
-
}
|
|
1787
|
-
function alignment(dep, target, located, common) {
|
|
1788
|
-
const local = target.version;
|
|
1789
|
-
if (!located || !local || !semver8.valid(local)) return null;
|
|
1790
|
-
const literal = located.version.value;
|
|
1791
|
-
if (!isUpgradableRequirement(literal) || satisfiesRequirement(literal, local)) return null;
|
|
1792
|
-
const floor = requirementFloor(literal) ?? "0.0.0";
|
|
1793
|
-
const diff = cargoDiff(floor, local);
|
|
1794
|
-
const tag = semver8.prerelease(local) ? extractPreReleaseTag(local) ?? "PRE" : void 0;
|
|
1795
|
-
return {
|
|
1796
|
-
candidate: {
|
|
1797
|
-
coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "cargo" },
|
|
1798
|
-
currentRange: literal,
|
|
1799
|
-
currentClean: floor,
|
|
1800
|
-
latest: local,
|
|
1801
|
-
newRange: rewriteRequirement(literal, local),
|
|
1802
|
-
diff,
|
|
1803
|
-
scope: common.scope,
|
|
1804
|
-
selected: diff !== "major",
|
|
1805
|
-
origin: { kind: "workspace", moduleId: target.id, moduleRelPath: target.relPath },
|
|
1806
|
-
action: "align",
|
|
1807
|
-
declaration: located.declaration,
|
|
1808
|
-
availableVersions: [local],
|
|
1809
|
-
isPreRelease: tag !== void 0,
|
|
1810
|
-
preReleaseTag: tag,
|
|
1811
|
-
...common.qualifier ? { qualifier: common.qualifier } : {}
|
|
1812
|
-
}
|
|
1813
|
-
};
|
|
1814
|
-
}
|
|
1815
|
-
function groupDependencies(deps) {
|
|
1816
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1817
|
-
for (const dep of deps) {
|
|
1818
|
-
const id = `${dep.rename ?? dep.name}`;
|
|
1819
|
-
groups.set(id, [...groups.get(id) ?? [], dep]);
|
|
1820
|
-
}
|
|
1821
|
-
return [...groups.values()];
|
|
1822
|
-
}
|
|
1823
|
-
function readDeclarations(manifestPath) {
|
|
1824
|
-
try {
|
|
1825
|
-
return parseCargoManifest(readFileSync11(manifestPath, "utf8"));
|
|
1826
|
-
} catch {
|
|
1827
|
-
return [];
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
var sectionOf = (dep) => dep.kind === "dev" ? "dev-dependencies" : dep.kind === "build" ? "build-dependencies" : "dependencies";
|
|
1831
|
-
function findDeclaration(decls, key, dep) {
|
|
1832
|
-
const candidates = decls.filter((d) => !d.inWorkspace && d.key === key);
|
|
1833
|
-
return candidates.find((d) => d.section === sectionOf(dep) && (d.target ?? null) === dep.target) ?? candidates[0];
|
|
1834
|
-
}
|
|
1835
|
-
function locate(decl, root, key, repoRoot, manifestPath, rootManifest) {
|
|
1836
|
-
if (!decl) return void 0;
|
|
1837
|
-
if (decl.inherited) {
|
|
1838
|
-
const shared = root.find((d) => d.inWorkspace && d.key === key);
|
|
1839
|
-
if (!shared?.version) return void 0;
|
|
1840
|
-
return {
|
|
1841
|
-
version: shared.version,
|
|
1842
|
-
inherited: true,
|
|
1843
|
-
declaration: { file: rootManifest, kind: "dependency-management", displayPath: display(repoRoot, rootManifest) }
|
|
1844
|
-
};
|
|
1845
|
-
}
|
|
1846
|
-
if (!decl.version) return void 0;
|
|
1847
|
-
return {
|
|
1848
|
-
version: decl.version,
|
|
1849
|
-
inherited: false,
|
|
1850
|
-
declaration: { file: manifestPath, kind: "dependency", displayPath: display(repoRoot, manifestPath) }
|
|
1851
|
-
};
|
|
1852
|
-
}
|
|
1853
|
-
function sharersOf(metadata, self, key) {
|
|
1854
|
-
return metadata.packages.filter((p) => p !== self).filter((p) => readDeclarations(p.manifest_path).some((d) => d.key === key && d.inherited)).map((p) => p.name);
|
|
1855
|
-
}
|
|
1856
|
-
var display = (repoRoot, file) => relative4(repoRoot, file).replace(/\\/g, "/");
|
|
1857
|
-
var samePath = (a, b) => relative4(a, b) === "";
|
|
1858
|
-
function sameRequirement(literal, req) {
|
|
1859
|
-
const a = toSemverRange(literal);
|
|
1860
|
-
const b = toSemverRange(req);
|
|
1861
|
-
return a !== null && b !== null && semver8.validRange(a) === semver8.validRange(b);
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
// src/adapters/cargo/cargo-updater.ts
|
|
1865
|
-
import { existsSync as existsSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync2 } from "fs";
|
|
1866
|
-
import { join as join12, resolve as resolve6 } from "path";
|
|
1867
|
-
import semver9 from "semver";
|
|
1868
|
-
|
|
1869
|
-
// src/adapters/shared/xml.ts
|
|
1870
|
-
import { SaxesParser } from "saxes";
|
|
1871
|
-
var XmlParseError = class extends Error {
|
|
1872
|
-
constructor(message2, file) {
|
|
1873
|
-
super(file ? `${file}: ${message2}` : message2);
|
|
1874
|
-
this.file = file;
|
|
1875
|
-
this.name = "XmlParseError";
|
|
1876
|
-
}
|
|
1877
|
-
file;
|
|
1878
|
-
};
|
|
1879
|
-
function parseXml(source, file) {
|
|
1880
|
-
const parser = new SaxesParser({ position: true });
|
|
1881
|
-
const stack = [];
|
|
1882
|
-
const openEnds = [];
|
|
1883
|
-
let root;
|
|
1884
|
-
let failure;
|
|
1885
|
-
parser.on("error", (err) => {
|
|
1886
|
-
failure ??= err;
|
|
1887
|
-
});
|
|
1888
|
-
parser.on("opentag", (tag) => {
|
|
1889
|
-
const attributes = {};
|
|
1890
|
-
for (const [key, value] of Object.entries(tag.attributes)) {
|
|
1891
|
-
attributes[key] = typeof value === "string" ? value : value.value;
|
|
1892
|
-
}
|
|
1893
|
-
const element = {
|
|
1894
|
-
name: localName(tag.name),
|
|
1895
|
-
attributes,
|
|
1896
|
-
children: [],
|
|
1897
|
-
text: ""
|
|
1898
|
-
};
|
|
1899
|
-
const parent = stack[stack.length - 1];
|
|
1900
|
-
if (parent) parent.children.push(element);
|
|
1901
|
-
else root ??= element;
|
|
1902
|
-
stack.push(element);
|
|
1903
|
-
openEnds.push(parser.position);
|
|
1904
|
-
});
|
|
1905
|
-
parser.on("closetag", (tag) => {
|
|
1906
|
-
const element = stack.pop();
|
|
1907
|
-
const openEnd = openEnds.pop();
|
|
1908
|
-
if (!element || openEnd === void 0 || tag.isSelfClosing) return;
|
|
1909
|
-
const closeEnd = parser.position;
|
|
1910
|
-
const closeStart = source.lastIndexOf("</", closeEnd - 1);
|
|
1911
|
-
if (closeStart >= openEnd) {
|
|
1912
|
-
element.contentRange = { start: openEnd, end: closeStart };
|
|
1913
|
-
}
|
|
1914
|
-
});
|
|
1915
|
-
parser.on("text", (text) => {
|
|
1916
|
-
const current = stack[stack.length - 1];
|
|
1917
|
-
if (current) current.text += text;
|
|
1918
|
-
});
|
|
1919
|
-
parser.on("cdata", (data) => {
|
|
1920
|
-
const current = stack[stack.length - 1];
|
|
1921
|
-
if (current) current.text += data;
|
|
1922
|
-
});
|
|
1923
|
-
try {
|
|
1924
|
-
parser.write(source).close();
|
|
1925
|
-
} catch (err) {
|
|
1926
|
-
failure ??= err;
|
|
1927
|
-
}
|
|
1928
|
-
if (failure) throw new XmlParseError(failure.message, file);
|
|
1929
|
-
if (!root) throw new XmlParseError("document has no root element", file);
|
|
1930
|
-
return root;
|
|
1931
|
-
}
|
|
1932
|
-
function child(element, name) {
|
|
1933
|
-
return element?.children.find((c) => c.name === name);
|
|
1934
|
-
}
|
|
1935
|
-
function children(element, name) {
|
|
1936
|
-
return element ? element.children.filter((c) => c.name === name) : [];
|
|
1937
|
-
}
|
|
1938
|
-
function childAt(element, ...path) {
|
|
1939
|
-
let current = element;
|
|
1940
|
-
for (const name of path) current = child(current, name);
|
|
1941
|
-
return current;
|
|
1942
|
-
}
|
|
1943
|
-
function childText(element, name) {
|
|
1944
|
-
const value = child(element, name)?.text.trim();
|
|
1945
|
-
return value ? value : void 0;
|
|
1946
|
-
}
|
|
1947
|
-
var StaleEditError = class extends Error {
|
|
1948
|
-
constructor(message2) {
|
|
1949
|
-
super(message2);
|
|
1950
|
-
this.name = "StaleEditError";
|
|
1951
|
-
}
|
|
1952
|
-
};
|
|
1953
|
-
function applyTextEdits(source, edits) {
|
|
1954
|
-
const splices = edits.map((edit) => {
|
|
1955
|
-
const range = edit.element.contentRange;
|
|
1956
|
-
if (!range) {
|
|
1957
|
-
throw new StaleEditError(`<${edit.element.name}> has no editable text content.`);
|
|
1958
|
-
}
|
|
1959
|
-
const raw = source.slice(range.start, range.end);
|
|
1960
|
-
const leading = raw.length - raw.trimStart().length;
|
|
1961
|
-
const value = raw.trim();
|
|
1962
|
-
if (value !== edit.expected) {
|
|
1963
|
-
throw new StaleEditError(
|
|
1964
|
-
`<${edit.element.name}> contains '${value}', expected '${edit.expected}'. The file changed since it was scanned.`
|
|
1965
|
-
);
|
|
1966
|
-
}
|
|
1967
|
-
const start = range.start + leading;
|
|
1968
|
-
return { start, end: start + value.length, replacement: edit.replacement };
|
|
1969
|
-
});
|
|
1970
|
-
splices.sort((a, b) => b.start - a.start);
|
|
1971
|
-
for (let i = 1; i < splices.length; i++) {
|
|
1972
|
-
if (splices[i].end > splices[i - 1].start) {
|
|
1973
|
-
throw new StaleEditError("Overlapping edits on the same element.");
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
let result = source;
|
|
1977
|
-
for (const splice of splices) {
|
|
1978
|
-
result = result.slice(0, splice.start) + splice.replacement + result.slice(splice.end);
|
|
1979
|
-
}
|
|
1980
|
-
return result;
|
|
1981
|
-
}
|
|
1982
|
-
function escapeXml(value) {
|
|
1983
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1984
|
-
}
|
|
1985
|
-
function serializeXml(element, indent = "") {
|
|
1986
|
-
const attrs = Object.entries(element.attributes).map(([key, value]) => ` ${key}="${escapeXml(value).replace(/"/g, """)}"`).join("");
|
|
1987
|
-
if (element.children.length === 0) {
|
|
1988
|
-
return `${indent}<${element.name}${attrs}>${escapeXml(element.text.trim())}</${element.name}>`;
|
|
1989
|
-
}
|
|
1990
|
-
const inner = element.children.map((c) => serializeXml(c, `${indent} `)).join("\n");
|
|
1991
|
-
return `${indent}<${element.name}${attrs}>
|
|
1992
|
-
${inner}
|
|
1993
|
-
${indent}</${element.name}>`;
|
|
1994
|
-
}
|
|
1995
|
-
function localName(qualified) {
|
|
1996
|
-
const colon = qualified.indexOf(":");
|
|
1997
|
-
return colon >= 0 ? qualified.slice(colon + 1) : qualified;
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
|
-
// src/adapters/shared/text-edit.ts
|
|
2001
|
-
function applyRangeEdits(source, edits) {
|
|
2002
|
-
for (const edit of edits) {
|
|
2003
|
-
const found = source.slice(edit.start, edit.end);
|
|
2004
|
-
if (found !== edit.expected) {
|
|
2005
|
-
throw new StaleEditError(`Found '${found}', expected '${edit.expected}'. The file changed since it was scanned.`);
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
2008
|
-
const ordered = [...edits].sort((a, b) => b.start - a.start);
|
|
2009
|
-
for (let i = 1; i < ordered.length; i++) {
|
|
2010
|
-
if (ordered[i].end > ordered[i - 1].start) throw new StaleEditError("Overlapping edits on the same text.");
|
|
2011
|
-
}
|
|
2012
|
-
let result = source;
|
|
2013
|
-
for (const edit of ordered) result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);
|
|
2014
|
-
return result;
|
|
2015
|
-
}
|
|
2016
|
-
|
|
2017
|
-
// src/adapters/cargo/cargo-updater.ts
|
|
2018
|
-
var UPDATE_TIMEOUT_MS = 10 * 6e4;
|
|
2019
|
-
var workspaceDir = (module, rootDir) => module.aggregatorDir ?? module.path ?? rootDir;
|
|
2020
|
-
var CargoUpdaterAdapter = class {
|
|
2021
|
-
affectedFiles(module, rootDir, updates) {
|
|
2022
|
-
const files = new Set(updates.map((u) => resolve6(u.declaration?.file ?? join12(module.path, "Cargo.toml"))));
|
|
2023
|
-
files.add(join12(workspaceDir(module, rootDir), "Cargo.lock"));
|
|
2024
|
-
return [...files];
|
|
2025
|
-
}
|
|
2026
|
-
async applyUpdates(module, rootDir, updates, onProgress) {
|
|
2027
|
-
const dir = workspaceDir(module, rootDir);
|
|
2028
|
-
const lockFile = join12(dir, "Cargo.lock");
|
|
2029
|
-
const lockedBefore = readLockedVersions(lockFile);
|
|
2030
|
-
const lockUpdates = updates.filter((u) => u.action === "lock");
|
|
2031
|
-
updates = updates.filter((u) => u.action !== "lock");
|
|
2032
|
-
this.checkLockUpdates(lockUpdates, lockFile, lockedBefore);
|
|
2033
|
-
const byFile = /* @__PURE__ */ new Map();
|
|
2034
|
-
for (const update of updates) {
|
|
2035
|
-
const file = resolve6(update.declaration?.file ?? join12(module.path, "Cargo.toml"));
|
|
2036
|
-
byFile.set(file, [...byFile.get(file) ?? [], update]);
|
|
2037
|
-
}
|
|
2038
|
-
const writes = [];
|
|
2039
|
-
for (const [file, fileUpdates] of byFile) {
|
|
2040
|
-
onProgress(`Preparing ${fileUpdates.length} change(s) in ${file}\u2026`);
|
|
2041
|
-
const source = readFileSync12(file, "utf8");
|
|
2042
|
-
const decls = parseCargoManifest(source);
|
|
2043
|
-
const edits = [];
|
|
2044
|
-
for (const update of fileUpdates) {
|
|
2045
|
-
const inWorkspace = update.declaration?.kind === "dependency-management";
|
|
2046
|
-
const name = update.coordinate.identifier;
|
|
2047
|
-
const found = decls.filter(
|
|
2048
|
-
(d) => d.inWorkspace === inWorkspace && (d.package ?? d.key) === name && d.version?.value === update.currentRange
|
|
2049
|
-
);
|
|
2050
|
-
if (found.length === 0) {
|
|
2051
|
-
throw new InstallError(`${file}: cannot find the version of ${name} (${update.currentRange}) any more; rescan the module.`);
|
|
2052
|
-
}
|
|
2053
|
-
for (const d of found) {
|
|
2054
|
-
edits.push({ start: d.version.start, end: d.version.end, expected: d.version.value, replacement: update.newRange });
|
|
2055
|
-
}
|
|
2056
|
-
}
|
|
2057
|
-
try {
|
|
2058
|
-
writes.push({ file, content: applyRangeEdits(source, edits) });
|
|
2059
|
-
} catch (err) {
|
|
2060
|
-
if (err instanceof StaleEditError) throw new InstallError(`${file}: ${err.message}`);
|
|
2061
|
-
throw err;
|
|
2062
|
-
}
|
|
2063
|
-
}
|
|
2064
|
-
onProgress("Writing Cargo.toml files\u2026");
|
|
2065
|
-
for (const write of writes) writeFileSync2(write.file, write.content, "utf8");
|
|
2066
|
-
const registryUpdates = updates.filter((u) => u.action === "update");
|
|
2067
|
-
if (existsSync8(lockFile) && registryUpdates.length > 0) {
|
|
2068
|
-
await this.refreshLock(dir, lockFile, lockedBefore, registryUpdates, onProgress);
|
|
2069
|
-
}
|
|
2070
|
-
for (const update of lockUpdates) {
|
|
2071
|
-
const name = update.coordinate.identifier;
|
|
2072
|
-
onProgress(`Locking ${name} at ${update.latest}\u2026`);
|
|
2073
|
-
await this.cargoUpdate(dir, ["-p", `${name}@${update.currentClean}`, "--precise", update.latest]);
|
|
2074
|
-
}
|
|
2075
|
-
const parts = [];
|
|
2076
|
-
if (updates.length > 0) {
|
|
2077
|
-
parts.push(`Updated ${updates.length} requirement(s) in ${writes.length} Cargo.toml file(s)${existsSync8(lockFile) ? "; Cargo.lock refreshed" : ""}`);
|
|
2078
|
-
}
|
|
2079
|
-
if (lockUpdates.length > 0) parts.push(`locked ${lockUpdates.length} crate(s) at a newer version in Cargo.lock`);
|
|
2080
|
-
const message2 = parts.join("; ");
|
|
2081
|
-
return {
|
|
2082
|
-
updatedCount: updates.length + lockUpdates.length,
|
|
2083
|
-
auditMessage: `${message2.charAt(0).toUpperCase()}${message2.slice(1)}.`,
|
|
2084
|
-
auditSeverity: "clean",
|
|
2085
|
-
integrityViolations: []
|
|
2086
|
-
};
|
|
2087
|
-
}
|
|
2088
|
-
/** A lock-only update needs a lock file that still holds the version the scan saw. */
|
|
2089
|
-
checkLockUpdates(lockUpdates, lockFile, locked) {
|
|
2090
|
-
if (lockUpdates.length === 0) return;
|
|
2091
|
-
if (!existsSync8(lockFile)) {
|
|
2092
|
-
throw new InstallError(`${lockFile} does not exist any more; rescan the module.`);
|
|
2093
|
-
}
|
|
2094
|
-
for (const update of lockUpdates) {
|
|
2095
|
-
const name = update.coordinate.identifier;
|
|
2096
|
-
if (!(locked.get(name) ?? []).includes(update.currentClean)) {
|
|
2097
|
-
throw new InstallError(`Cargo.lock no longer locks ${name} at ${update.currentClean}; rescan the module.`);
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
async resync() {
|
|
2102
|
-
}
|
|
2103
|
-
async refreshLock(dir, lockFile, before, updates, onProgress) {
|
|
2104
|
-
const specs = [];
|
|
2105
|
-
for (const update of updates) {
|
|
2106
|
-
const name = update.coordinate.identifier;
|
|
2107
|
-
const versions = before.get(name) ?? [];
|
|
2108
|
-
const old = versions.find((v) => satisfiesRequirement(update.currentRange, v)) ?? versions[0];
|
|
2109
|
-
if (old) specs.push(`${name}@${old}`);
|
|
2110
|
-
}
|
|
2111
|
-
if (specs.length === 0) return;
|
|
2112
|
-
onProgress("Updating Cargo.lock\u2026");
|
|
2113
|
-
await this.cargoUpdate(dir, specs.flatMap((s) => ["-p", s]));
|
|
2114
|
-
const after = readLockedVersions(lockFile);
|
|
2115
|
-
for (const update of updates) {
|
|
2116
|
-
const name = update.coordinate.identifier;
|
|
2117
|
-
const locked = (after.get(name) ?? []).find((v) => satisfiesRequirement(update.newRange, v));
|
|
2118
|
-
if (locked && semver9.valid(update.latest) && locked !== update.latest) {
|
|
2119
|
-
onProgress(`Pinning ${name} to ${update.latest}\u2026`);
|
|
2120
|
-
await this.cargoUpdate(dir, ["-p", `${name}@${locked}`, "--precise", update.latest]);
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
}
|
|
2124
|
-
async cargoUpdate(dir, args) {
|
|
2125
|
-
const result = await runCommand("cargo", ["update", ...args], { cwd: dir, timeoutMs: UPDATE_TIMEOUT_MS });
|
|
2126
|
-
if (result.exitCode !== 0) {
|
|
2127
|
-
const reason = result.timedOut ? "cargo update timed out" : "cargo update failed";
|
|
2128
|
-
throw new InstallError(reason, tail(result.stderr || result.stdout));
|
|
2129
|
-
}
|
|
2130
|
-
}
|
|
2131
|
-
};
|
|
2132
|
-
|
|
2133
|
-
// src/adapters/cargo/cargo-verifier.ts
|
|
2134
|
-
import { existsSync as existsSync9 } from "fs";
|
|
2135
|
-
import { join as join13, relative as relative5, resolve as resolve7 } from "path";
|
|
2136
|
-
var VERIFY_TIMEOUT_MS2 = 30 * 6e4;
|
|
2137
|
-
var same = (a, b) => relative5(resolve7(a), resolve7(b)) === "";
|
|
2138
|
-
function planCheck(metadata, module, changedFiles, locked) {
|
|
2139
|
-
const args = ["check", "--all-targets", ...locked ? ["--locked"] : []];
|
|
2140
|
-
const rootManifest = join13(metadata.workspace_root, "Cargo.toml");
|
|
2141
|
-
const lockFile = join13(metadata.workspace_root, "Cargo.lock");
|
|
2142
|
-
const memberManifests = new Set(metadata.packages.map((p) => resolve7(p.manifest_path)));
|
|
2143
|
-
if (changedFiles.some((f) => same(f, rootManifest))) {
|
|
2144
|
-
const isVirtualRoot = !memberManifests.has(resolve7(rootManifest));
|
|
2145
|
-
if (isVirtualRoot || metadata.packages.length > 1) return [...args, "--workspace"];
|
|
2146
|
-
}
|
|
2147
|
-
if (changedFiles.length > 0 && changedFiles.every((f) => same(f, lockFile))) return [...args, "--workspace"];
|
|
2148
|
-
const changed = new Set(
|
|
2149
|
-
metadata.packages.filter((p) => changedFiles.some((f) => same(f, p.manifest_path))).map((p) => p.name)
|
|
2150
|
-
);
|
|
2151
|
-
if (changed.size === 0) changed.add(module.id);
|
|
2152
|
-
for (let grew = true; grew; ) {
|
|
2153
|
-
grew = false;
|
|
2154
|
-
for (const p of metadata.packages) {
|
|
2155
|
-
if (changed.has(p.name)) continue;
|
|
2156
|
-
if (p.dependencies.some((d) => d.source === null && d.path && changed.has(d.name))) {
|
|
2157
|
-
changed.add(p.name);
|
|
2158
|
-
grew = true;
|
|
2159
|
-
}
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
const ordered = metadata.packages.filter((p) => changed.has(p.name)).map((p) => p.name);
|
|
2163
|
-
return [...args, ...ordered.flatMap((name) => ["-p", name])];
|
|
2164
|
-
}
|
|
2165
|
-
var CargoVerificationAdapter = class {
|
|
2166
|
-
constructor(deps = { loadMetadata, hasLock: (dir) => existsSync9(join13(dir, "Cargo.lock")) }) {
|
|
2167
|
-
this.deps = deps;
|
|
2168
|
-
}
|
|
2169
|
-
deps;
|
|
2170
|
-
async verify(module, rootDir, options = {}) {
|
|
2171
|
-
const dir = module.aggregatorDir ?? module.path ?? rootDir;
|
|
2172
|
-
if (options.customScript) {
|
|
2173
|
-
const label2 = options.customLabel || options.customScript;
|
|
2174
|
-
const result2 = await runShellScript(options.customScript, { cwd: dir, timeoutMs: VERIFY_TIMEOUT_MS2 });
|
|
2175
|
-
return result2.exitCode === 0 ? { status: "clean", details: "Verification script succeeded.", label: label2 } : { status: "warn", details: tail(result2.stderr || result2.stdout), label: label2 };
|
|
2176
|
-
}
|
|
2177
|
-
const metadata = await this.deps.loadMetadata(dir);
|
|
2178
|
-
const args = planCheck(metadata, module, options.changedFiles ?? [], this.deps.hasLock(dir));
|
|
2179
|
-
const label = `Cargo build (cargo ${args.join(" ")})`;
|
|
2180
|
-
const result = await runCargo(args, { cwd: dir, timeoutMs: VERIFY_TIMEOUT_MS2 });
|
|
2181
|
-
if (result.exitCode === 0) return { status: "clean", details: "All affected crates type-check, including tests.", label };
|
|
2182
|
-
return {
|
|
2183
|
-
status: "warn",
|
|
2184
|
-
details: result.timedOut ? "cargo check timed out." : cargoErrors(`${result.stderr}
|
|
2185
|
-
${result.stdout}`),
|
|
2186
|
-
label
|
|
2187
|
-
};
|
|
2188
|
-
}
|
|
2189
|
-
};
|
|
2190
|
-
|
|
2191
|
-
// src/adapters/cargo/cargo-strategy.ts
|
|
2192
|
-
var cargoVersionPolicy = {
|
|
2193
|
-
normalize: (v) => semver10.valid(v),
|
|
2194
|
-
rewrite: rewriteRequirement,
|
|
2195
|
-
diff: (current, target) => semver10.valid(current) && semver10.valid(target) ? semver10.eq(current, target) ? "patch" : cargoDiff(current, target) : "minor",
|
|
2196
|
-
// Updates are written where the scan found the declaration.
|
|
2197
|
-
allowsManualTargets: false
|
|
2198
|
-
};
|
|
2199
|
-
var CargoEcosystemStrategy = class {
|
|
2200
|
-
ecosystem = "cargo";
|
|
2201
|
-
displayName = "Rust / Cargo";
|
|
2202
|
-
icon = "\u{1F980}";
|
|
2203
|
-
manifestFile = "Cargo.toml";
|
|
2204
|
-
versioning = cargoVersionPolicy;
|
|
2205
|
-
config = new CargoConfigReader();
|
|
2206
|
-
index = new CargoIndexClient();
|
|
2207
|
-
endpoint = (registry, cwd) => this.config.resolve(registry, cwd);
|
|
2208
|
-
discovery = new CargoModuleDiscoveryAdapter();
|
|
2209
|
-
registry = new CargoRegistryAdapter({ index: this.index, endpoint: this.endpoint });
|
|
2210
|
-
reader = new CargoDependencyAdapter({ loadMetadata, index: this.index, endpoint: this.endpoint });
|
|
2211
|
-
updater = new CargoUpdaterAdapter();
|
|
2212
|
-
verifier = new CargoVerificationAdapter();
|
|
2213
|
-
getTabLabels(counts, t) {
|
|
2214
|
-
return {
|
|
2215
|
-
all: t.tabs.all(counts.total),
|
|
2216
|
-
prod: t.tabs.dependencies(counts.prodCount),
|
|
2217
|
-
dev: t.tabs.devDependencies(counts.devCount)
|
|
2218
|
-
};
|
|
2219
|
-
}
|
|
2220
|
-
};
|
|
2221
|
-
|
|
2222
|
-
// src/adapters/maven/maven-discovery.ts
|
|
2223
|
-
import { existsSync as existsSync12 } from "fs";
|
|
2224
|
-
import { dirname as dirname7, join as join16, relative as relative6, resolve as resolve10 } from "path";
|
|
2225
|
-
|
|
2226
|
-
// src/adapters/maven/maven-project.ts
|
|
2227
|
-
import { existsSync as existsSync11, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
2228
|
-
import { join as join15, resolve as resolve9 } from "path";
|
|
2229
|
-
|
|
2230
|
-
// src/adapters/maven/maven-pom.ts
|
|
2231
|
-
import { existsSync as existsSync10, readFileSync as readFileSync13, statSync } from "fs";
|
|
2232
|
-
import { dirname as dirname6, join as join14, resolve as resolve8 } from "path";
|
|
2233
|
-
function readPom(file) {
|
|
2234
|
-
const source = readFileSync13(file, "utf8");
|
|
2235
|
-
return parsePom(source, file);
|
|
2236
|
-
}
|
|
2237
|
-
function parsePom(source, file) {
|
|
2238
|
-
const project = parseXml(source, file);
|
|
2239
|
-
const parentElement = child(project, "parent");
|
|
2240
|
-
const parent = parentElement ? {
|
|
2241
|
-
groupId: childText(parentElement, "groupId") ?? "",
|
|
2242
|
-
artifactId: childText(parentElement, "artifactId") ?? "",
|
|
2243
|
-
version: childText(parentElement, "version") ?? "",
|
|
2244
|
-
relativePath: child(parentElement, "relativePath")?.text.trim(),
|
|
2245
|
-
versionElement: child(parentElement, "version")
|
|
2246
|
-
} : void 0;
|
|
2247
|
-
const properties = /* @__PURE__ */ new Map();
|
|
2248
|
-
for (const element of child(project, "properties")?.children ?? []) {
|
|
2249
|
-
properties.set(element.name, { value: element.text.trim(), element });
|
|
2250
|
-
}
|
|
2251
|
-
return {
|
|
2252
|
-
file,
|
|
2253
|
-
dir: dirname6(file),
|
|
2254
|
-
groupId: childText(project, "groupId"),
|
|
2255
|
-
artifactId: childText(project, "artifactId") ?? "unknown",
|
|
2256
|
-
version: childText(project, "version"),
|
|
2257
|
-
versionElement: child(project, "version"),
|
|
2258
|
-
name: childText(project, "name"),
|
|
2259
|
-
packaging: childText(project, "packaging") ?? "jar",
|
|
2260
|
-
parent,
|
|
2261
|
-
properties,
|
|
2262
|
-
dependencies: readDependencies(child(project, "dependencies")),
|
|
2263
|
-
managedDependencies: readDependencies(childAt(project, "dependencyManagement", "dependencies")),
|
|
2264
|
-
modules: children(child(project, "modules"), "module").map((m) => m.text.trim()).filter(Boolean)
|
|
2265
|
-
};
|
|
2266
|
-
}
|
|
2267
|
-
function readDependencies(container) {
|
|
2268
|
-
return children(container, "dependency").map((element) => ({
|
|
2269
|
-
groupId: childText(element, "groupId") ?? "",
|
|
2270
|
-
artifactId: childText(element, "artifactId") ?? "",
|
|
2271
|
-
version: childText(element, "version"),
|
|
2272
|
-
type: childText(element, "type"),
|
|
2273
|
-
classifier: childText(element, "classifier"),
|
|
2274
|
-
scope: childText(element, "scope"),
|
|
2275
|
-
versionElement: child(element, "version")
|
|
2276
|
-
}));
|
|
2277
|
-
}
|
|
2278
|
-
function parentPomFile(pom) {
|
|
2279
|
-
if (!pom.parent) return void 0;
|
|
2280
|
-
const relativePath = pom.parent.relativePath ?? "../pom.xml";
|
|
2281
|
-
if (relativePath === "") return void 0;
|
|
2282
|
-
let candidate = resolve8(pom.dir, relativePath);
|
|
2283
|
-
if (existsSync10(candidate) && statSync(candidate).isDirectory()) candidate = join14(candidate, "pom.xml");
|
|
2284
|
-
return existsSync10(candidate) ? candidate : void 0;
|
|
2285
|
-
}
|
|
2286
|
-
function builtinProperty(name, module) {
|
|
2287
|
-
switch (name) {
|
|
2288
|
-
case "project.groupId":
|
|
2289
|
-
case "pom.groupId":
|
|
2290
|
-
return module.groupId ?? module.parent?.groupId;
|
|
2291
|
-
case "project.artifactId":
|
|
2292
|
-
case "pom.artifactId":
|
|
2293
|
-
return module.artifactId;
|
|
2294
|
-
case "project.version":
|
|
2295
|
-
case "pom.version":
|
|
2296
|
-
return module.version ?? module.parent?.version;
|
|
2297
|
-
case "project.parent.groupId":
|
|
2298
|
-
return module.parent?.groupId;
|
|
2299
|
-
case "project.parent.version":
|
|
2300
|
-
return module.parent?.version;
|
|
2301
|
-
default:
|
|
2302
|
-
return void 0;
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
function findProperty(name, chain) {
|
|
2306
|
-
for (const pom of chain) {
|
|
2307
|
-
const property = pom.properties.get(name);
|
|
2308
|
-
if (property) return { pom, property };
|
|
2309
|
-
}
|
|
2310
|
-
return void 0;
|
|
2311
|
-
}
|
|
2312
|
-
function interpolate(text, chain, depth = 0) {
|
|
2313
|
-
if (depth > 10 || !text.includes("${")) return text;
|
|
2314
|
-
const module = chain[0];
|
|
2315
|
-
const replaced = text.replace(/\$\{([^}]+)\}/g, (expression, name) => {
|
|
2316
|
-
const builtin = module ? builtinProperty(name, module) : void 0;
|
|
2317
|
-
if (builtin !== void 0) return builtin;
|
|
2318
|
-
return findProperty(name, chain)?.property.value ?? expression;
|
|
2319
|
-
});
|
|
2320
|
-
return replaced === text ? text : interpolate(replaced, chain, depth + 1);
|
|
2321
|
-
}
|
|
2322
|
-
function propertyReference(text) {
|
|
2323
|
-
return text.match(/^\$\{([^}]+)\}$/)?.[1];
|
|
2324
|
-
}
|
|
2325
|
-
|
|
2326
|
-
// src/adapters/maven/maven-project.ts
|
|
2327
|
-
var MAX_DEPTH = 6;
|
|
2328
|
-
var SKIPPED_DIRS = /* @__PURE__ */ new Set(["node_modules", "target", "build", "dist", "out"]);
|
|
2329
|
-
var MavenProject = class _MavenProject {
|
|
2330
|
-
constructor(rootDir, poms) {
|
|
2331
|
-
this.rootDir = rootDir;
|
|
2332
|
-
this.poms = poms;
|
|
2333
|
-
for (const pom of poms) this.byFile.set(pom.file, pom);
|
|
2334
|
-
this.assignAggregators();
|
|
2335
|
-
}
|
|
2336
|
-
rootDir;
|
|
2337
|
-
poms;
|
|
2338
|
-
byFile = /* @__PURE__ */ new Map();
|
|
2339
|
-
aggregators = /* @__PURE__ */ new Map();
|
|
2340
|
-
/**
|
|
2341
|
-
* Loads every readable pom.xml below `rootDir`, plus every module listed in
|
|
2342
|
-
* a `<modules>` section, even in directories the file walk skips (such as
|
|
2343
|
-
* `build/` or `dist/`). Malformed files are skipped.
|
|
2344
|
-
*/
|
|
2345
|
-
static load(rootDir) {
|
|
2346
|
-
const poms = [];
|
|
2347
|
-
const queue = findPomFiles(rootDir);
|
|
2348
|
-
const seen = new Set(queue);
|
|
2349
|
-
while (queue.length > 0) {
|
|
2350
|
-
const file = queue.shift();
|
|
2351
|
-
let pom;
|
|
2352
|
-
try {
|
|
2353
|
-
pom = readPom(file);
|
|
2354
|
-
} catch {
|
|
2355
|
-
continue;
|
|
2356
|
-
}
|
|
2357
|
-
poms.push(pom);
|
|
2358
|
-
for (const entry of pom.modules) {
|
|
2359
|
-
const target = resolve9(pom.dir, entry);
|
|
2360
|
-
const moduleFile = target.endsWith(".xml") ? target : join15(target, "pom.xml");
|
|
2361
|
-
if (!seen.has(moduleFile) && existsSync11(moduleFile)) {
|
|
2362
|
-
seen.add(moduleFile);
|
|
2363
|
-
queue.push(moduleFile);
|
|
2364
|
-
}
|
|
2365
|
-
}
|
|
2366
|
-
}
|
|
2367
|
-
return new _MavenProject(rootDir, poms);
|
|
2368
|
-
}
|
|
2369
|
-
pomAt(file) {
|
|
2370
|
-
return this.byFile.get(resolve9(file));
|
|
2371
|
-
}
|
|
2372
|
-
/** The project followed by its local parents, stopping at the first external parent. */
|
|
2373
|
-
chainOf(pom) {
|
|
2374
|
-
const poms = [pom];
|
|
2375
|
-
let current = pom;
|
|
2376
|
-
while (current.parent) {
|
|
2377
|
-
const parent = this.localParentOf(current);
|
|
2378
|
-
if (!parent || poms.includes(parent)) {
|
|
2379
|
-
return { poms, externalParent: parent ? void 0 : current.parent };
|
|
2380
|
-
}
|
|
2381
|
-
poms.push(parent);
|
|
2382
|
-
current = parent;
|
|
2383
|
-
}
|
|
2384
|
-
return { poms };
|
|
2385
|
-
}
|
|
2386
|
-
/** Coordinates of a project with inherited groupId/version and interpolated values. */
|
|
2387
|
-
coordinatesOf(pom) {
|
|
2388
|
-
const chain = this.chainOf(pom).poms;
|
|
2389
|
-
const groupId = pom.groupId ?? pom.parent?.groupId ?? "";
|
|
2390
|
-
const rawVersion = pom.version ?? pom.parent?.version;
|
|
2391
|
-
const version = rawVersion ? interpolate(rawVersion, chain) : void 0;
|
|
2392
|
-
return {
|
|
2393
|
-
groupId: interpolate(groupId, chain),
|
|
2394
|
-
artifactId: pom.artifactId,
|
|
2395
|
-
version: version && !version.includes("${") ? version : void 0
|
|
2396
|
-
};
|
|
2397
|
-
}
|
|
2398
|
-
/** Directory of the outermost aggregator whose reactor includes this project. */
|
|
2399
|
-
aggregatorDirOf(pom) {
|
|
2400
|
-
return this.aggregators.get(pom.file);
|
|
2401
|
-
}
|
|
2402
|
-
localParentOf(pom) {
|
|
2403
|
-
const parent = pom.parent;
|
|
2404
|
-
if (!parent) return void 0;
|
|
2405
|
-
const matches = (candidate) => {
|
|
2406
|
-
if (!candidate) return false;
|
|
2407
|
-
const groupId = interpolate(candidate.groupId ?? candidate.parent?.groupId ?? "", [candidate]);
|
|
2408
|
-
const version = interpolate(candidate.version ?? candidate.parent?.version ?? "", [candidate]);
|
|
2409
|
-
const expected = interpolate(parent.version, [pom]);
|
|
2410
|
-
return groupId === parent.groupId && candidate.artifactId === parent.artifactId && (version.includes("${") || expected.includes("${") || version === expected);
|
|
2411
|
-
};
|
|
2412
|
-
const file = parentPomFile(pom);
|
|
2413
|
-
const byPath = file ? this.byFile.get(resolve9(file)) : void 0;
|
|
2414
|
-
return matches(byPath) ? byPath : void 0;
|
|
2415
|
-
}
|
|
2416
|
-
/**
|
|
2417
|
-
* Walks `<modules>` from every aggregator, outermost first, and records for
|
|
2418
|
-
* each reached project the aggregator that builds it.
|
|
2419
|
-
*/
|
|
2420
|
-
assignAggregators() {
|
|
2421
|
-
const aggregatorPoms = this.poms.filter((pom) => pom.modules.length > 0).sort((a, b) => a.dir.length - b.dir.length);
|
|
2422
|
-
for (const aggregator of aggregatorPoms) {
|
|
2423
|
-
if (this.aggregators.has(aggregator.file)) continue;
|
|
2424
|
-
const queue = [aggregator];
|
|
2425
|
-
while (queue.length > 0) {
|
|
2426
|
-
const pom = queue.shift();
|
|
2427
|
-
if (this.aggregators.has(pom.file)) continue;
|
|
2428
|
-
this.aggregators.set(pom.file, aggregator.dir);
|
|
2429
|
-
for (const entry of pom.modules) {
|
|
2430
|
-
const target = resolve9(pom.dir, entry);
|
|
2431
|
-
const file = target.endsWith(".xml") ? target : join15(target, "pom.xml");
|
|
2432
|
-
const modulePom = this.byFile.get(file);
|
|
2433
|
-
if (modulePom) queue.push(modulePom);
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
}
|
|
2438
|
-
};
|
|
2439
|
-
function findPomFiles(rootDir) {
|
|
2440
|
-
const results = [];
|
|
2441
|
-
const rootPom = join15(rootDir, "pom.xml");
|
|
2442
|
-
if (existsSync11(rootPom)) results.push(resolve9(rootPom));
|
|
2443
|
-
const walk = (dir, depth) => {
|
|
2444
|
-
if (depth > MAX_DEPTH) return;
|
|
2445
|
-
let entries2;
|
|
2446
|
-
try {
|
|
2447
|
-
entries2 = readdirSync2(dir, { withFileTypes: true });
|
|
2448
|
-
} catch {
|
|
2449
|
-
return;
|
|
2450
|
-
}
|
|
2451
|
-
const isProject = existsSync11(join15(dir, "pom.xml"));
|
|
2452
|
-
for (const entry of entries2) {
|
|
2453
|
-
if (!entry.isDirectory() || entry.name.startsWith(".") || SKIPPED_DIRS.has(entry.name)) continue;
|
|
2454
|
-
if (isProject && entry.name === "src") continue;
|
|
2455
|
-
const subDir = join15(dir, entry.name);
|
|
2456
|
-
const pom = join15(subDir, "pom.xml");
|
|
2457
|
-
if (existsSync11(pom) && statSync2(pom).isFile()) results.push(resolve9(pom));
|
|
2458
|
-
walk(subDir, depth + 1);
|
|
2459
|
-
}
|
|
2460
|
-
};
|
|
2461
|
-
walk(rootDir, 1);
|
|
2462
|
-
return results;
|
|
2463
|
-
}
|
|
2464
|
-
|
|
2465
|
-
// src/adapters/maven/maven-discovery.ts
|
|
2466
|
-
var MavenModuleDiscoveryAdapter = class {
|
|
2467
|
-
/**
|
|
2468
|
-
* Proposes a root only when `startDir` holds a pom.xml, and then climbs to
|
|
2469
|
-
* the POM above until there is none: the top of the connected POM chain.
|
|
2470
|
-
*/
|
|
2471
|
-
findRoot(startDir, boundaryDir) {
|
|
2472
|
-
let dir = resolve10(startDir);
|
|
2473
|
-
if (!existsSync12(join16(dir, "pom.xml"))) return void 0;
|
|
2474
|
-
const boundary = boundaryDir ? resolve10(boundaryDir) : void 0;
|
|
2475
|
-
for (; ; ) {
|
|
2476
|
-
const above = pomAbove(dir, boundary);
|
|
2477
|
-
if (!above) return dir;
|
|
2478
|
-
dir = above;
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
async discover(rootDir) {
|
|
2482
|
-
const project = MavenProject.load(rootDir);
|
|
2483
|
-
const modules = project.poms.map((pom) => {
|
|
2484
|
-
const coordinates = project.coordinatesOf(pom);
|
|
2485
|
-
const relPath = relative6(rootDir, pom.dir).replace(/\\/g, "/");
|
|
2486
|
-
return {
|
|
2487
|
-
id: coordinates.groupId ? `${coordinates.groupId}:${coordinates.artifactId}` : coordinates.artifactId,
|
|
2488
|
-
name: pom.name ? interpolate(pom.name, project.chainOf(pom).poms) : pom.artifactId,
|
|
2489
|
-
path: pom.dir,
|
|
2490
|
-
relPath: relPath || "Root",
|
|
2491
|
-
ecosystem: "maven",
|
|
2492
|
-
isRoot: relPath === "",
|
|
2493
|
-
version: coordinates.version,
|
|
2494
|
-
aggregatorDir: project.aggregatorDirOf(pom)
|
|
2495
|
-
};
|
|
2496
|
-
});
|
|
2497
|
-
modules.sort((a, b) => a.isRoot ? -1 : b.isRoot ? 1 : a.relPath.localeCompare(b.relPath));
|
|
2498
|
-
return modules;
|
|
2499
|
-
}
|
|
2500
|
-
};
|
|
2501
|
-
function tryReadPom(file) {
|
|
2502
|
-
try {
|
|
2503
|
-
return readPom(file);
|
|
2504
|
-
} catch {
|
|
2505
|
-
return void 0;
|
|
2506
|
-
}
|
|
2507
|
-
}
|
|
2508
|
-
function ancestorsWithin(dir, boundary) {
|
|
2509
|
-
const result = [];
|
|
2510
|
-
let current = dir;
|
|
2511
|
-
while (current !== boundary) {
|
|
2512
|
-
const parent = dirname7(current);
|
|
2513
|
-
if (parent === current) break;
|
|
2514
|
-
result.push(parent);
|
|
2515
|
-
current = parent;
|
|
2516
|
-
}
|
|
2517
|
-
return result;
|
|
2518
|
-
}
|
|
2519
|
-
function pomAbove(dir, boundary) {
|
|
2520
|
-
const pom = tryReadPom(join16(dir, "pom.xml"));
|
|
2521
|
-
if (!pom) return void 0;
|
|
2522
|
-
const ancestors = ancestorsWithin(dir, boundary);
|
|
2523
|
-
const found = [];
|
|
2524
|
-
const parentFile = parentPomFile(pom);
|
|
2525
|
-
if (parentFile && pom.parent && ancestors.includes(dirname7(parentFile))) {
|
|
2526
|
-
const parent = tryReadPom(parentFile);
|
|
2527
|
-
const groupId = parent?.groupId ?? parent?.parent?.groupId;
|
|
2528
|
-
if (parent && parent.artifactId === pom.parent.artifactId && groupId === pom.parent.groupId) {
|
|
2529
|
-
found.push(dirname7(parentFile));
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
|
-
const ownFile = join16(dir, "pom.xml");
|
|
2533
|
-
const aggregator = ancestors.find(
|
|
2534
|
-
(ancestor) => tryReadPom(join16(ancestor, "pom.xml"))?.modules.some((entry) => {
|
|
2535
|
-
const target = resolve10(ancestor, entry);
|
|
2536
|
-
return target === dir || target === ownFile;
|
|
2537
|
-
})
|
|
2538
|
-
);
|
|
2539
|
-
if (aggregator) found.push(aggregator);
|
|
2540
|
-
return found.sort((a, b) => a.length - b.length)[0];
|
|
2541
|
-
}
|
|
2542
|
-
|
|
2543
|
-
// src/adapters/maven/maven-registry.ts
|
|
2544
|
-
import { existsSync as existsSync15 } from "fs";
|
|
2545
|
-
import { join as join19 } from "path";
|
|
2546
|
-
|
|
2547
|
-
// src/adapters/maven/maven-command.ts
|
|
2548
|
-
import { existsSync as existsSync13 } from "fs";
|
|
2549
|
-
import { dirname as dirname8, isAbsolute as isAbsolute2, join as join17, relative as relative7, resolve as resolve11, sep } from "path";
|
|
2550
|
-
var MavenUnavailableError = class extends Error {
|
|
2551
|
-
constructor() {
|
|
2552
|
-
super("Maven is not available: no Maven Wrapper (mvnw) in the project and no `mvn` on the PATH.");
|
|
2553
|
-
this.name = "MavenUnavailableError";
|
|
2554
|
-
}
|
|
2555
|
-
};
|
|
2556
|
-
var MavenCommandError = class extends Error {
|
|
2557
|
-
constructor(message2, output) {
|
|
2558
|
-
super(message2);
|
|
2559
|
-
this.output = output;
|
|
2560
|
-
this.name = "MavenCommandError";
|
|
2561
|
-
}
|
|
2562
|
-
output;
|
|
2563
|
-
};
|
|
2564
|
-
var IS_WINDOWS2 = process.platform === "win32";
|
|
2565
|
-
var WRAPPER = IS_WINDOWS2 ? "mvnw.cmd" : "mvnw";
|
|
2566
|
-
var BATCH_ARGS = ["-B", "--no-transfer-progress"];
|
|
2567
|
-
var mvnOnPath;
|
|
2568
|
-
function findMavenWrapper(fromDir, rootDir) {
|
|
2569
|
-
let dir = resolve11(fromDir);
|
|
2570
|
-
const root = resolve11(rootDir);
|
|
2571
|
-
for (; ; ) {
|
|
2572
|
-
const candidate = join17(dir, WRAPPER);
|
|
2573
|
-
if (existsSync13(candidate)) return candidate;
|
|
2574
|
-
if (dir === root || relative7(root, dir).startsWith("..")) return void 0;
|
|
2575
|
-
const parent = dirname8(dir);
|
|
2576
|
-
if (parent === dir) return void 0;
|
|
2577
|
-
dir = parent;
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
function relativeToCwd(cwd, file) {
|
|
2581
|
-
const path = relative7(cwd, file);
|
|
2582
|
-
return isAbsolute2(path) ? file : path || ".";
|
|
2583
|
-
}
|
|
2584
|
-
async function runMaven(args, options) {
|
|
2585
|
-
const wrapper = findMavenWrapper(options.cwd, options.rootDir);
|
|
2586
|
-
const run = { cwd: options.cwd, timeoutMs: options.timeoutMs };
|
|
2587
|
-
if (wrapper) {
|
|
2588
|
-
const relativeWrapper = relativeToCwd(options.cwd, wrapper);
|
|
2589
|
-
const path = isAbsolute2(relativeWrapper) || relativeWrapper.includes(sep) ? relativeWrapper : `.${sep}${relativeWrapper}`;
|
|
2590
|
-
return IS_WINDOWS2 ? runCommand(path, [...BATCH_ARGS, ...args], run) : runCommand("sh", [path, ...BATCH_ARGS, ...args], run);
|
|
2591
|
-
}
|
|
2592
|
-
mvnOnPath ??= runCommand("mvn", ["-v"], { cwd: options.cwd, timeoutMs: 3e4 }).then(
|
|
2593
|
-
(result) => result.exitCode === 0,
|
|
2594
|
-
() => false
|
|
2595
|
-
);
|
|
2596
|
-
if (!await mvnOnPath) throw new MavenUnavailableError();
|
|
2597
|
-
return runCommand("mvn", [...BATCH_ARGS, ...args], run);
|
|
2598
|
-
}
|
|
2599
|
-
function mavenErrors(output) {
|
|
2600
|
-
const errors = output.split(/\r?\n/).filter((line) => line.startsWith("[ERROR]")).map((line) => line.replace(/^\[ERROR\]\s?/, "")).filter((line) => line.trim() && !line.startsWith("->") && !line.includes("Re-run Maven"));
|
|
2601
|
-
return (errors.length > 0 ? errors : output.trim().split(/\r?\n/).slice(-10)).slice(0, 12).join("\n");
|
|
2602
|
-
}
|
|
2603
|
-
|
|
2604
|
-
// src/adapters/maven/maven-resolution.ts
|
|
2605
|
-
import { randomUUID } from "crypto";
|
|
2606
|
-
import { existsSync as existsSync14, mkdirSync, readFileSync as readFileSync14, readdirSync as readdirSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2607
|
-
import { join as join18 } from "path";
|
|
2608
|
-
var DEFAULT_MAVEN_PLUGINS = { help: "3.5.2", versions: "2.22.0" };
|
|
2609
|
-
var HELP_PLUGIN_ID = "org.apache.maven.plugins:maven-help-plugin";
|
|
2610
|
-
var VERSIONS_PLUGIN_ID = "org.codehaus.mojo:versions-maven-plugin";
|
|
2611
|
-
var DOWNLOAD_FAILURES = [
|
|
2612
|
-
"could not be resolved",
|
|
2613
|
-
"could not find artifact",
|
|
2614
|
-
"failed to read artifact descriptor",
|
|
2615
|
-
"could not transfer artifact"
|
|
2616
|
-
];
|
|
2617
|
-
var MavenPluginUnavailableError = class extends MavenCommandError {
|
|
2618
|
-
constructor(plugin, output) {
|
|
2619
|
-
super(
|
|
2620
|
-
`Maven could not download ${plugin.id}:${plugin.version}, which Elevate uses to ${plugin.purpose}. Make this plugin available in the repository or mirror your Maven is configured with, or choose a version that is available there with "mavenPlugins": { "${plugin.key}": "<version>" } in elevate.config.json.
|
|
2621
|
-
${mavenErrors(output)}`,
|
|
2622
|
-
output
|
|
2623
|
-
);
|
|
2624
|
-
this.name = "MavenPluginUnavailableError";
|
|
2625
|
-
}
|
|
2626
|
-
};
|
|
2627
|
-
function isPluginUnavailable(output, pluginId) {
|
|
2628
|
-
return output.split(/\r?\n/).some((line) => {
|
|
2629
|
-
const lower = line.toLowerCase();
|
|
2630
|
-
return line.includes(pluginId) && DOWNLOAD_FAILURES.some((phrase) => lower.includes(phrase));
|
|
2631
|
-
});
|
|
2632
|
-
}
|
|
2633
|
-
var MAVEN_TIMEOUT_MS = 5 * 6e4;
|
|
2634
|
-
var ScratchDirectory = class {
|
|
2635
|
-
path;
|
|
2636
|
-
target;
|
|
2637
|
-
createdTarget;
|
|
2638
|
-
constructor(moduleDir) {
|
|
2639
|
-
this.target = join18(moduleDir, "target");
|
|
2640
|
-
this.createdTarget = !existsSync14(this.target);
|
|
2641
|
-
this.path = join18(this.target, `.elevate-${randomUUID().slice(0, 8)}`);
|
|
2642
|
-
mkdirSync(this.path, { recursive: true });
|
|
2643
|
-
}
|
|
2644
|
-
dispose() {
|
|
2645
|
-
rmSync(this.path, { recursive: true, force: true });
|
|
2646
|
-
try {
|
|
2647
|
-
if (this.createdTarget && readdirSync3(this.target).length === 0) rmSync(this.target, { recursive: true });
|
|
2648
|
-
} catch {
|
|
2649
|
-
}
|
|
2650
|
-
}
|
|
2651
|
-
};
|
|
2652
|
-
async function readEffectivePoms(pomFile, moduleDir, rootDir, scratch, plugins, recursive = false) {
|
|
2653
|
-
const output = join18(scratch.path, "effective-pom.xml");
|
|
2654
|
-
const args = [
|
|
2655
|
-
"-f",
|
|
2656
|
-
relativeToCwd(moduleDir, pomFile),
|
|
2657
|
-
`${HELP_PLUGIN_ID}:${plugins.help}:effective-pom`,
|
|
2658
|
-
`-Doutput=${relativeToCwd(moduleDir, output)}`
|
|
2659
|
-
];
|
|
2660
|
-
const result = await runMaven(recursive ? args : ["-N", ...args], {
|
|
2661
|
-
cwd: moduleDir,
|
|
2662
|
-
rootDir,
|
|
2663
|
-
timeoutMs: MAVEN_TIMEOUT_MS
|
|
2664
|
-
});
|
|
2665
|
-
if (result.exitCode !== 0 || !existsSync14(output)) {
|
|
2666
|
-
const text = `${result.stdout}
|
|
2667
|
-
${result.stderr}`;
|
|
2668
|
-
if (isPluginUnavailable(text, HELP_PLUGIN_ID)) {
|
|
2669
|
-
throw new MavenPluginUnavailableError(
|
|
2670
|
-
{ id: HELP_PLUGIN_ID, version: plugins.help, key: "help", purpose: "read the effective POM" },
|
|
2671
|
-
text
|
|
2672
|
-
);
|
|
2673
|
-
}
|
|
2674
|
-
throw new MavenCommandError(`Maven could not build the effective POM:
|
|
2675
|
-
${mavenErrors(text)}`, text);
|
|
2676
|
-
}
|
|
2677
|
-
return parseEffectivePoms(readFileSync14(output, "utf8"));
|
|
2678
|
-
}
|
|
2679
|
-
function parseEffectivePoms(source) {
|
|
2680
|
-
const root = parseXml(source);
|
|
2681
|
-
const projects = root.name === "projects" ? children(root, "project") : [root];
|
|
2682
|
-
const models = /* @__PURE__ */ new Map();
|
|
2683
|
-
for (const project of projects) {
|
|
2684
|
-
models.set(`${childText(project, "groupId")}:${childText(project, "artifactId")}`, toEffectiveModel(project));
|
|
2685
|
-
}
|
|
2686
|
-
return models;
|
|
2687
|
-
}
|
|
2688
|
-
function mergeRepositories(models) {
|
|
2689
|
-
const merge2 = (name, pick) => {
|
|
2690
|
-
const byId = /* @__PURE__ */ new Map();
|
|
2691
|
-
for (const element of models.map(pick)) {
|
|
2692
|
-
for (const repository of element?.children ?? []) {
|
|
2693
|
-
const id = childText(repository, "id") ?? childText(repository, "url") ?? "";
|
|
2694
|
-
if (!byId.has(id)) byId.set(id, repository);
|
|
2695
|
-
}
|
|
2696
|
-
}
|
|
2697
|
-
return byId.size > 0 ? { name, attributes: {}, children: [...byId.values()], text: "" } : void 0;
|
|
2698
|
-
};
|
|
2699
|
-
return {
|
|
2700
|
-
dependencies: [],
|
|
2701
|
-
managedDependencies: [],
|
|
2702
|
-
repositories: merge2("repositories", (m) => m.repositories),
|
|
2703
|
-
pluginRepositories: merge2("pluginRepositories", (m) => m.pluginRepositories)
|
|
2704
|
-
};
|
|
2705
|
-
}
|
|
2706
|
-
function toEffectiveModel(project) {
|
|
2707
|
-
return {
|
|
2708
|
-
dependencies: readEffectiveDependencies(child(project, "dependencies")),
|
|
2709
|
-
managedDependencies: readEffectiveDependencies(childAt(project, "dependencyManagement", "dependencies")),
|
|
2710
|
-
repositories: child(project, "repositories"),
|
|
2711
|
-
pluginRepositories: child(project, "pluginRepositories")
|
|
2712
|
-
};
|
|
2713
|
-
}
|
|
2714
|
-
function readEffectiveDependencies(container) {
|
|
2715
|
-
return children(container, "dependency").map((element) => ({
|
|
2716
|
-
groupId: childText(element, "groupId") ?? "",
|
|
2717
|
-
artifactId: childText(element, "artifactId") ?? "",
|
|
2718
|
-
version: childText(element, "version") ?? "",
|
|
2719
|
-
scope: childText(element, "scope") ?? "compile",
|
|
2720
|
-
type: childText(element, "type") ?? "jar",
|
|
2721
|
-
classifier: childText(element, "classifier")
|
|
2722
|
-
}));
|
|
2723
|
-
}
|
|
2724
|
-
function buildProbePom(artifacts, model) {
|
|
2725
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2726
|
-
const entries2 = [];
|
|
2727
|
-
for (const artifact of artifacts) {
|
|
2728
|
-
const key = `${artifact.groupId}:${artifact.artifactId}:${artifact.type ?? "jar"}:${artifact.classifier ?? ""}`;
|
|
2729
|
-
if (seen.has(key)) continue;
|
|
2730
|
-
seen.add(key);
|
|
2731
|
-
const fields = [
|
|
2732
|
-
`<groupId>${escapeXml(artifact.groupId)}</groupId>`,
|
|
2733
|
-
`<artifactId>${escapeXml(artifact.artifactId)}</artifactId>`,
|
|
2734
|
-
`<version>${escapeXml(artifact.version)}</version>`,
|
|
2735
|
-
artifact.type && artifact.type !== "jar" ? `<type>${escapeXml(artifact.type)}</type>` : "",
|
|
2736
|
-
artifact.classifier ? `<classifier>${escapeXml(artifact.classifier)}</classifier>` : ""
|
|
2737
|
-
].filter(Boolean);
|
|
2738
|
-
entries2.push(` <dependency>${fields.join("")}</dependency>`);
|
|
2739
|
-
}
|
|
2740
|
-
const repositories = [model.repositories, model.pluginRepositories].filter((element) => element !== void 0).map((element) => serializeXml(element, " ")).join("\n");
|
|
2741
|
-
return [
|
|
2742
|
-
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
2743
|
-
'<project xmlns="http://maven.apache.org/POM/4.0.0">',
|
|
2744
|
-
" <modelVersion>4.0.0</modelVersion>",
|
|
2745
|
-
" <groupId>elevate.probe</groupId>",
|
|
2746
|
-
" <artifactId>elevate-probe</artifactId>",
|
|
2747
|
-
" <version>0</version>",
|
|
2748
|
-
" <packaging>pom</packaging>",
|
|
2749
|
-
repositories,
|
|
2750
|
-
" <dependencyManagement>",
|
|
2751
|
-
" <dependencies>",
|
|
2752
|
-
...entries2,
|
|
2753
|
-
" </dependencies>",
|
|
2754
|
-
" </dependencyManagement>",
|
|
2755
|
-
"</project>",
|
|
2756
|
-
""
|
|
2757
|
-
].filter((line) => line !== "").join("\n");
|
|
2758
|
-
}
|
|
2759
|
-
async function queryNewerVersions(artifacts, model, moduleDir, rootDir, scratch, plugins) {
|
|
2760
|
-
if (artifacts.length === 0) return { newerVersions: /* @__PURE__ */ new Map(), failedLookups: /* @__PURE__ */ new Set() };
|
|
2761
|
-
const probeDir = join18(scratch.path, "probe");
|
|
2762
|
-
mkdirSync(probeDir, { recursive: true });
|
|
2763
|
-
const probePom = join18(probeDir, "pom.xml");
|
|
2764
|
-
writeFileSync3(probePom, buildProbePom(artifacts, model), "utf8");
|
|
2765
|
-
const result = await runMaven(
|
|
2766
|
-
[
|
|
2767
|
-
"-N",
|
|
2768
|
-
"-f",
|
|
2769
|
-
relativeToCwd(moduleDir, probePom),
|
|
2770
|
-
`${VERSIONS_PLUGIN_ID}:${plugins.versions}:dependency-updates-report`,
|
|
2771
|
-
"-DdependencyUpdatesReportFormats=xml",
|
|
2772
|
-
"-DprocessDependencyManagementTransitive=false"
|
|
2773
|
-
],
|
|
2774
|
-
{ cwd: moduleDir, rootDir, timeoutMs: MAVEN_TIMEOUT_MS }
|
|
2775
|
-
);
|
|
2776
|
-
const log = `${result.stdout}
|
|
2777
|
-
${result.stderr}`;
|
|
2778
|
-
const reportFile = join18(probeDir, "target", "dependency-updates-report.xml");
|
|
2779
|
-
if (result.exitCode !== 0 || !existsSync14(reportFile)) {
|
|
2780
|
-
if (isPluginUnavailable(log, VERSIONS_PLUGIN_ID)) {
|
|
2781
|
-
throw new MavenPluginUnavailableError(
|
|
2782
|
-
{ id: VERSIONS_PLUGIN_ID, version: plugins.versions, key: "versions", purpose: "look up newer versions" },
|
|
2783
|
-
log
|
|
2784
|
-
);
|
|
2785
|
-
}
|
|
2786
|
-
throw new MavenCommandError(`Maven could not look up newer versions:
|
|
2787
|
-
${mavenErrors(log)}`, log);
|
|
2788
|
-
}
|
|
2789
|
-
return {
|
|
2790
|
-
newerVersions: parseUpdatesReport(readFileSync14(reportFile, "utf8")),
|
|
2791
|
-
failedLookups: parseFailedLookups(log)
|
|
2792
|
-
};
|
|
2793
|
-
}
|
|
2794
|
-
function parseUpdatesReport(source) {
|
|
2795
|
-
const report = parseXml(source);
|
|
2796
|
-
const sections = [
|
|
2797
|
-
...children(child(report, "dependencyManagements"), "dependencyManagement"),
|
|
2798
|
-
...children(child(report, "dependencies"), "dependency")
|
|
2799
|
-
];
|
|
2800
|
-
const result = /* @__PURE__ */ new Map();
|
|
2801
|
-
for (const entry of sections) {
|
|
2802
|
-
const key = `${childText(entry, "groupId")}:${childText(entry, "artifactId")}`;
|
|
2803
|
-
const versions = [
|
|
2804
|
-
...collect(entry, "subIncrementals", "subIncremental"),
|
|
2805
|
-
...collect(entry, "incrementals", "incremental"),
|
|
2806
|
-
...collect(entry, "minors", "minor"),
|
|
2807
|
-
...collect(entry, "majors", "major")
|
|
2808
|
-
];
|
|
2809
|
-
const known = result.get(key) ?? [];
|
|
2810
|
-
for (const version of versions) if (!known.includes(version)) known.push(version);
|
|
2811
|
-
result.set(key, known);
|
|
2812
|
-
}
|
|
2813
|
-
return result;
|
|
2814
|
-
}
|
|
2815
|
-
function collect(entry, container, item) {
|
|
2816
|
-
return children(child(entry, container), item).map((element) => element.text.trim()).filter(Boolean);
|
|
2817
|
-
}
|
|
2818
|
-
function parseFailedLookups(log) {
|
|
2819
|
-
const failed = /* @__PURE__ */ new Set();
|
|
2820
|
-
const pattern = /Could not (?:transfer|read) metadata ([^:\s/]+):([^:\s/]+)(?::[^\s/]+)?\/maven-metadata\.xml/g;
|
|
2821
|
-
for (const match of log.matchAll(pattern)) failed.add(`${match[1]}:${match[2]}`);
|
|
2822
|
-
return failed;
|
|
2823
|
-
}
|
|
2824
|
-
|
|
2825
|
-
// src/adapters/maven/maven-registry.ts
|
|
2826
|
-
var PROBE_VERSION = "0";
|
|
2827
|
-
var NO_REPOSITORIES = { dependencies: [], managedDependencies: [] };
|
|
2828
|
-
var MavenRegistryAdapter = class {
|
|
2829
|
-
constructor(plugins = DEFAULT_MAVEN_PLUGINS) {
|
|
2830
|
-
this.plugins = plugins;
|
|
2831
|
-
}
|
|
2832
|
-
plugins;
|
|
2833
|
-
/**
|
|
2834
|
-
* The repositories come from Maven's own configuration, which Elevate does
|
|
2835
|
-
* not interpret. They are reported as non-public: the lookup goes exactly
|
|
2836
|
-
* where the project's build would go.
|
|
2837
|
-
*/
|
|
2838
|
-
async resolveEndpoint() {
|
|
2839
|
-
return { url: "repositories configured for Maven (settings.xml, POM)", isPublic: false };
|
|
2840
|
-
}
|
|
2841
|
-
async getLatestVersion(coordinate, channel = "stable", cwd = process.cwd()) {
|
|
2842
|
-
const versions = await this.getAllVersions(coordinate, cwd);
|
|
2843
|
-
return versions.find((v) => channel === "all" || !isPreReleaseVersion(v)) ?? null;
|
|
2844
|
-
}
|
|
2845
|
-
/**
|
|
2846
|
-
* All versions available in the configured repositories, newest first.
|
|
2847
|
-
* Throws `MavenCommandError` when Maven fails or a repository could not be
|
|
2848
|
-
* read, so an error is never mistaken for "no versions".
|
|
2849
|
-
*/
|
|
2850
|
-
async getAllVersions(coordinate, cwd = process.cwd()) {
|
|
2851
|
-
if (!coordinate.group || !coordinate.artifact) return [];
|
|
2852
|
-
const key = `${coordinate.group}:${coordinate.artifact}`;
|
|
2853
|
-
const scratch = new ScratchDirectory(cwd);
|
|
2854
|
-
try {
|
|
2855
|
-
const repositories = await projectRepositories(cwd, scratch, this.plugins);
|
|
2856
|
-
const report = await queryNewerVersions(
|
|
2857
|
-
[{ groupId: coordinate.group, artifactId: coordinate.artifact, version: PROBE_VERSION }],
|
|
2858
|
-
repositories,
|
|
2859
|
-
cwd,
|
|
2860
|
-
cwd,
|
|
2861
|
-
scratch,
|
|
2862
|
-
this.plugins
|
|
2863
|
-
);
|
|
2864
|
-
const versions = report.newerVersions.get(key) ?? [];
|
|
2865
|
-
if (versions.length === 0 && report.failedLookups.has(key)) {
|
|
2866
|
-
throw new MavenCommandError(`A Maven repository failed to deliver the versions of ${key}.`, "");
|
|
2867
|
-
}
|
|
2868
|
-
return [...versions].reverse();
|
|
2869
|
-
} finally {
|
|
2870
|
-
scratch.dispose();
|
|
2871
|
-
}
|
|
2872
|
-
}
|
|
2873
|
-
};
|
|
2874
|
-
async function projectRepositories(cwd, scratch, plugins) {
|
|
2875
|
-
const rootPom = join19(cwd, "pom.xml");
|
|
2876
|
-
if (!existsSync15(rootPom)) return NO_REPOSITORIES;
|
|
2877
|
-
const models = await readEffectivePoms(rootPom, cwd, cwd, scratch, plugins);
|
|
2878
|
-
return [...models.values()][0] ?? NO_REPOSITORIES;
|
|
2879
|
-
}
|
|
2880
|
-
|
|
2881
|
-
// src/adapters/maven/maven-scanner.ts
|
|
2882
|
-
import { join as join20 } from "path";
|
|
2883
|
-
import semver11 from "semver";
|
|
2884
|
-
|
|
2885
|
-
// src/adapters/maven/maven-locator.ts
|
|
2886
|
-
import { relative as relative8 } from "path";
|
|
2887
|
-
function locateDependencyVersion(chain, target, rootDir) {
|
|
2888
|
-
for (const pom of chain) {
|
|
2889
|
-
const dependency = findDependency(pom.dependencies, target, chain);
|
|
2890
|
-
if (!dependency) continue;
|
|
2891
|
-
if (!dependency.versionElement) break;
|
|
2892
|
-
return resolveLiteral(pom, "dependency", dependency.versionElement, chain, target, rootDir);
|
|
2893
|
-
}
|
|
2894
|
-
return locateManagedVersion(chain, target, rootDir);
|
|
2895
|
-
}
|
|
2896
|
-
function locateManagedVersion(chain, target, rootDir) {
|
|
2897
|
-
for (const pom of chain) {
|
|
2898
|
-
const managed = findDependency(pom.managedDependencies, target, chain);
|
|
2899
|
-
if (managed?.versionElement) {
|
|
2900
|
-
return resolveLiteral(pom, "dependency-management", managed.versionElement, chain, target, rootDir);
|
|
2901
|
-
}
|
|
2902
|
-
}
|
|
2903
|
-
return {
|
|
2904
|
-
found: false,
|
|
2905
|
-
reason: "managed-externally",
|
|
2906
|
-
detail: "version is managed outside this repository (external parent POM or imported BOM)"
|
|
2907
|
-
};
|
|
2908
|
-
}
|
|
2909
|
-
function locateParentVersion(module, target, rootDir) {
|
|
2910
|
-
const element = module.parent?.versionElement;
|
|
2911
|
-
if (!element) {
|
|
2912
|
-
return { found: false, reason: "declaration-mismatch", detail: "parent has no <version> element" };
|
|
2913
|
-
}
|
|
2914
|
-
return resolveLiteral(module, "parent", element, [module], target, rootDir);
|
|
2915
|
-
}
|
|
2916
|
-
function findDependency(dependencies, target, chain) {
|
|
2917
|
-
return dependencies.find(
|
|
2918
|
-
(dep) => dep.scope !== "import" && interpolate(dep.groupId, chain) === target.groupId && interpolate(dep.artifactId, chain) === target.artifactId
|
|
2919
|
-
);
|
|
2920
|
-
}
|
|
2921
|
-
function resolveLiteral(pom, kind, versionElement, chain, target, rootDir) {
|
|
2922
|
-
let file = pom.file;
|
|
2923
|
-
let element = versionElement;
|
|
2924
|
-
let value = versionElement.text.trim();
|
|
2925
|
-
let propertyName;
|
|
2926
|
-
for (let depth = 0; depth < 10; depth++) {
|
|
2927
|
-
const reference = propertyReference(value);
|
|
2928
|
-
if (!reference) break;
|
|
2929
|
-
if (reference.startsWith("project.") || reference.startsWith("pom.")) {
|
|
2930
|
-
return {
|
|
2931
|
-
found: false,
|
|
2932
|
-
reason: "declaration-mismatch",
|
|
2933
|
-
detail: `version is the expression \${${reference}}, not an editable value`
|
|
2934
|
-
};
|
|
2935
|
-
}
|
|
2936
|
-
const definition = findProperty(reference, chain);
|
|
2937
|
-
if (!definition) {
|
|
2938
|
-
return {
|
|
2939
|
-
found: false,
|
|
2940
|
-
reason: "managed-externally",
|
|
2941
|
-
detail: `property \${${reference}} is defined outside this repository`
|
|
2942
|
-
};
|
|
2943
|
-
}
|
|
2944
|
-
file = definition.pom.file;
|
|
2945
|
-
element = definition.property.element;
|
|
2946
|
-
value = definition.property.value;
|
|
2947
|
-
propertyName = reference;
|
|
2948
|
-
}
|
|
2949
|
-
if (value.includes("${")) {
|
|
2950
|
-
return {
|
|
2951
|
-
found: false,
|
|
2952
|
-
reason: "declaration-mismatch",
|
|
2953
|
-
detail: `version '${value}' combines several expressions`
|
|
2954
|
-
};
|
|
2955
|
-
}
|
|
2956
|
-
if (value !== target.effectiveVersion) {
|
|
2957
|
-
return {
|
|
2958
|
-
found: false,
|
|
2959
|
-
reason: "declaration-mismatch",
|
|
2960
|
-
detail: `declared '${value}' but Maven resolves '${target.effectiveVersion}' (profile or command-line override?)`
|
|
2961
|
-
};
|
|
2962
|
-
}
|
|
2963
|
-
const declaration = {
|
|
2964
|
-
file,
|
|
2965
|
-
kind: propertyName ? "property" : kind,
|
|
2966
|
-
propertyName,
|
|
2967
|
-
displayPath: relative8(rootDir, file).replace(/\\/g, "/")
|
|
2968
|
-
};
|
|
2969
|
-
return { found: true, declaration, element, value };
|
|
2970
|
-
}
|
|
2971
|
-
|
|
2972
|
-
// src/adapters/maven/maven-scanner.ts
|
|
2973
|
-
var DIFF_ORDER3 = { major: 0, minor: 1, patch: 2 };
|
|
2974
|
-
var MavenDependencyAdapter = class {
|
|
2975
|
-
constructor(plugins = DEFAULT_MAVEN_PLUGINS) {
|
|
2976
|
-
this.plugins = plugins;
|
|
2977
|
-
}
|
|
2978
|
-
plugins;
|
|
2979
|
-
async scan(module, context) {
|
|
2980
|
-
const results = await this.scanMany([module], context);
|
|
2981
|
-
return results.get(module) ?? { candidates: [], skipped: [] };
|
|
2982
|
-
}
|
|
2983
|
-
async scanMany(modules, context) {
|
|
2984
|
-
const project = MavenProject.load(context.rootDir);
|
|
2985
|
-
const results = /* @__PURE__ */ new Map();
|
|
2986
|
-
for (const group of groupModules(project, modules)) {
|
|
2987
|
-
const scratch = new ScratchDirectory(group.dir);
|
|
2988
|
-
try {
|
|
2989
|
-
for (const [module, result] of await this.scanGroup(project, group, context, scratch)) {
|
|
2990
|
-
results.set(module, result);
|
|
2991
|
-
}
|
|
2992
|
-
} finally {
|
|
2993
|
-
scratch.dispose();
|
|
2994
|
-
}
|
|
2995
|
-
}
|
|
2996
|
-
return results;
|
|
2997
|
-
}
|
|
2998
|
-
async scanGroup(project, group, context, scratch) {
|
|
2999
|
-
const rootDir = context.rootDir;
|
|
3000
|
-
const effectiveModels = group.aggregatorPom ? await readEffectivePoms(group.aggregatorPom, group.dir, rootDir, scratch, this.plugins, true) : await readEffectivePoms(group.members[0].pom.file, group.dir, rootDir, scratch, this.plugins);
|
|
3001
|
-
const plans = [];
|
|
3002
|
-
for (const { module, pom } of group.members) {
|
|
3003
|
-
const coordinates = project.coordinatesOf(pom);
|
|
3004
|
-
const effective = effectiveModels.get(`${coordinates.groupId}:${coordinates.artifactId}`) ?? (group.aggregatorPom ? [...(await readEffectivePoms(pom.file, module.path, rootDir, scratch, this.plugins)).values()][0] : [...effectiveModels.values()][0]);
|
|
3005
|
-
if (!effective) continue;
|
|
3006
|
-
plans.push(planModule(project, pom, module, effective, context));
|
|
3007
|
-
}
|
|
3008
|
-
const probe = plans.flatMap((plan) => plan.lookups.map(({ subject }) => subject));
|
|
3009
|
-
const report = await queryNewerVersions(
|
|
3010
|
-
probe,
|
|
3011
|
-
mergeRepositories(plans.map((plan) => plan.effective)),
|
|
3012
|
-
group.dir,
|
|
3013
|
-
rootDir,
|
|
3014
|
-
scratch,
|
|
3015
|
-
this.plugins
|
|
3016
|
-
);
|
|
3017
|
-
return new Map(plans.map((plan) => [plan.module, finishModule(plan, report, context.channel)]));
|
|
3018
|
-
}
|
|
3019
|
-
};
|
|
3020
|
-
function groupModules(project, modules) {
|
|
3021
|
-
const groups = /* @__PURE__ */ new Map();
|
|
3022
|
-
const reactorSizes = /* @__PURE__ */ new Map();
|
|
3023
|
-
for (const module of modules) {
|
|
3024
|
-
if (module.aggregatorDir) reactorSizes.set(module.aggregatorDir, (reactorSizes.get(module.aggregatorDir) ?? 0) + 1);
|
|
3025
|
-
}
|
|
3026
|
-
for (const module of modules) {
|
|
3027
|
-
const pom = project.pomAt(join20(module.path, "pom.xml"));
|
|
3028
|
-
if (!pom) continue;
|
|
3029
|
-
const reactor = module.aggregatorDir;
|
|
3030
|
-
const batched = reactor !== void 0 && (reactorSizes.get(reactor) ?? 0) > 1;
|
|
3031
|
-
const key = batched ? `reactor:${reactor}` : `module:${module.path}`;
|
|
3032
|
-
const group = groups.get(key) ?? {
|
|
3033
|
-
dir: batched ? reactor : module.path,
|
|
3034
|
-
aggregatorPom: batched ? join20(reactor, "pom.xml") : void 0,
|
|
3035
|
-
members: []
|
|
3036
|
-
};
|
|
3037
|
-
group.members.push({ module, pom });
|
|
3038
|
-
groups.set(key, group);
|
|
3039
|
-
}
|
|
3040
|
-
return [...groups.values()];
|
|
3041
|
-
}
|
|
3042
|
-
function planModule(project, pom, module, effective, context) {
|
|
3043
|
-
const { poms: chain, externalParent } = project.chainOf(pom);
|
|
3044
|
-
const rootDir = context.rootDir;
|
|
3045
|
-
const subjects = /* @__PURE__ */ new Map();
|
|
3046
|
-
for (const dep of effective.dependencies) {
|
|
3047
|
-
const key = `${dep.groupId}:${dep.artifactId}`;
|
|
3048
|
-
if (subjects.has(key)) continue;
|
|
3049
|
-
const target = { groupId: dep.groupId, artifactId: dep.artifactId };
|
|
3050
|
-
subjects.set(key, {
|
|
3051
|
-
...dep,
|
|
3052
|
-
scope: dep.scope === "test" ? "test" : "prod",
|
|
3053
|
-
locate: (effectiveVersion) => locateDependencyVersion(chain, { ...target, effectiveVersion }, rootDir)
|
|
3054
|
-
});
|
|
3055
|
-
}
|
|
3056
|
-
for (const managed of pom.managedDependencies) {
|
|
3057
|
-
const groupId = interpolate(managed.groupId, chain);
|
|
3058
|
-
const artifactId = interpolate(managed.artifactId, chain);
|
|
3059
|
-
const key = `${groupId}:${artifactId}`;
|
|
3060
|
-
if (subjects.has(key) || !managed.version) continue;
|
|
3061
|
-
const isImport = managed.scope === "import";
|
|
3062
|
-
const version = isImport ? interpolate(managed.version, chain) : effective.managedDependencies.find((m) => m.groupId === groupId && m.artifactId === artifactId)?.version;
|
|
3063
|
-
if (!version || version.includes("${")) continue;
|
|
3064
|
-
const target = { groupId, artifactId };
|
|
3065
|
-
subjects.set(key, {
|
|
3066
|
-
groupId,
|
|
3067
|
-
artifactId,
|
|
3068
|
-
version,
|
|
3069
|
-
type: isImport ? "pom" : managed.type,
|
|
3070
|
-
classifier: managed.classifier,
|
|
3071
|
-
scope: "prod",
|
|
3072
|
-
locate: (effectiveVersion) => locateManagedVersion(chain, { ...target, effectiveVersion }, rootDir)
|
|
3073
|
-
});
|
|
3074
|
-
}
|
|
3075
|
-
if (pom.parent && externalParent === pom.parent) {
|
|
3076
|
-
const { groupId, artifactId } = pom.parent;
|
|
3077
|
-
const version = interpolate(pom.parent.version, chain);
|
|
3078
|
-
subjects.set(`${groupId}:${artifactId}`, {
|
|
3079
|
-
groupId,
|
|
3080
|
-
artifactId,
|
|
3081
|
-
version,
|
|
3082
|
-
type: "pom",
|
|
3083
|
-
scope: "prod",
|
|
3084
|
-
locate: (effectiveVersion) => locateParentVersion(pom, { groupId, artifactId, effectiveVersion }, rootDir)
|
|
3085
|
-
});
|
|
3086
|
-
}
|
|
3087
|
-
const plan = { module, effective, candidates: [], skipped: [], lookups: [] };
|
|
3088
|
-
for (const [key, subject] of subjects) {
|
|
3089
|
-
const origin = classifyOrigin(key, "maven", context);
|
|
3090
|
-
if (origin.kind !== "workspace") {
|
|
3091
|
-
plan.lookups.push({ subject, origin });
|
|
3092
|
-
continue;
|
|
3093
|
-
}
|
|
3094
|
-
const target = context.workspace.get(key);
|
|
3095
|
-
if (target && inSameReactor(module, target)) {
|
|
3096
|
-
if (target.version && target.version !== subject.version) {
|
|
3097
|
-
collect2(alignmentCandidate2(subject, target.version, origin), plan.candidates, plan.skipped);
|
|
3098
|
-
}
|
|
3099
|
-
} else {
|
|
3100
|
-
plan.lookups.push({ subject, origin: classifyOrigin(key, "maven", { ...context, workspace: /* @__PURE__ */ new Map() }) });
|
|
3101
|
-
}
|
|
3102
|
-
}
|
|
3103
|
-
return plan;
|
|
3104
|
-
}
|
|
3105
|
-
function inSameReactor(a, b) {
|
|
3106
|
-
return a.aggregatorDir !== void 0 && a.aggregatorDir === b.aggregatorDir;
|
|
3107
|
-
}
|
|
3108
|
-
function finishModule(plan, report, channel) {
|
|
3109
|
-
const { candidates, skipped } = plan;
|
|
3110
|
-
for (const { subject, origin } of plan.lookups) {
|
|
3111
|
-
const key = `${subject.groupId}:${subject.artifactId}`;
|
|
3112
|
-
const newer = newerThan(subject.version, report.newerVersions.get(key) ?? []);
|
|
3113
|
-
const latest = pickLatest(subject.version, newer, channel);
|
|
3114
|
-
if (!latest) {
|
|
3115
|
-
if (report.failedLookups.has(key)) {
|
|
3116
|
-
skipped.push({ identifier: key, origin: origin.kind, reason: "lookup-failed", detail: "repository error" });
|
|
3117
|
-
}
|
|
3118
|
-
continue;
|
|
3119
|
-
}
|
|
3120
|
-
collect2(updateCandidate(subject, latest, newer, origin), candidates, skipped);
|
|
3121
|
-
}
|
|
3122
|
-
markSharedDeclarations(candidates);
|
|
3123
|
-
candidates.sort((a, b) => (DIFF_ORDER3[a.diff] ?? 3) - (DIFF_ORDER3[b.diff] ?? 3));
|
|
3124
|
-
return { candidates, skipped };
|
|
3125
|
-
}
|
|
3126
|
-
function newerThan(current, ascending) {
|
|
3127
|
-
const index = ascending.indexOf(current);
|
|
3128
|
-
return index >= 0 ? ascending.slice(index + 1) : [...ascending];
|
|
3129
|
-
}
|
|
3130
|
-
function collect2(built, candidates, skipped) {
|
|
3131
|
-
if ("candidate" in built) candidates.push(built.candidate);
|
|
3132
|
-
else skipped.push(built.skipped);
|
|
3133
|
-
}
|
|
3134
|
-
function updateCandidate(subject, latest, newer, origin) {
|
|
3135
|
-
const base = buildCandidate(subject, latest, origin, "update");
|
|
3136
|
-
if ("candidate" in base) {
|
|
3137
|
-
base.candidate.availableVersions = [...newer].reverse().concat(subject.version);
|
|
3138
|
-
}
|
|
3139
|
-
return base;
|
|
3140
|
-
}
|
|
3141
|
-
function alignmentCandidate2(subject, localVersion, origin) {
|
|
3142
|
-
const built = buildCandidate(subject, localVersion, origin, "align");
|
|
3143
|
-
if ("candidate" in built) built.candidate.availableVersions = [localVersion];
|
|
3144
|
-
return built;
|
|
3145
|
-
}
|
|
3146
|
-
function buildCandidate(subject, target, origin, action) {
|
|
3147
|
-
const identifier = `${subject.groupId}:${subject.artifactId}`;
|
|
3148
|
-
const location = subject.locate(subject.version);
|
|
3149
|
-
if (!location.found) {
|
|
3150
|
-
return { skipped: { identifier, origin: origin.kind, reason: location.reason, detail: location.detail } };
|
|
3151
|
-
}
|
|
3152
|
-
const currentClean = cleanJavaVersion(subject.version) ?? "0.0.0";
|
|
3153
|
-
const targetClean = cleanJavaVersion(target) ?? currentClean;
|
|
3154
|
-
const diff = semver11.eq(currentClean, targetClean) ? "patch" : diffVersions(currentClean, targetClean);
|
|
3155
|
-
const isPreRelease = isPreReleaseVersion(target);
|
|
3156
|
-
return {
|
|
3157
|
-
candidate: {
|
|
3158
|
-
coordinate: { identifier, group: subject.groupId, artifact: subject.artifactId, ecosystem: "maven" },
|
|
3159
|
-
currentRange: subject.version,
|
|
3160
|
-
currentClean,
|
|
3161
|
-
latest: target,
|
|
3162
|
-
newRange: target,
|
|
3163
|
-
diff,
|
|
3164
|
-
scope: subject.scope,
|
|
3165
|
-
selected: diff !== "major",
|
|
3166
|
-
origin,
|
|
3167
|
-
action,
|
|
3168
|
-
declaration: location.declaration,
|
|
3169
|
-
isPreRelease,
|
|
3170
|
-
preReleaseTag: isPreRelease ? extractPreReleaseTag(target) : void 0
|
|
3171
|
-
}
|
|
3172
|
-
};
|
|
3173
|
-
}
|
|
3174
|
-
function pickLatest(current, newer, channel) {
|
|
3175
|
-
const allowed = newer.filter((v) => channel === "all" || !isPreReleaseVersion(v));
|
|
3176
|
-
const flavour = flavourOf(current);
|
|
3177
|
-
const sameFlavour = flavour ? allowed.filter((v) => flavourOf(v) === flavour) : [];
|
|
3178
|
-
const pool = sameFlavour.length > 0 ? sameFlavour : allowed;
|
|
3179
|
-
return pool[pool.length - 1];
|
|
3180
|
-
}
|
|
3181
|
-
function flavourOf(version) {
|
|
3182
|
-
const suffix = version.match(/-([A-Za-z]+)$/)?.[1];
|
|
3183
|
-
return suffix && !isPreReleaseVersion(version) ? suffix.toLowerCase() : void 0;
|
|
3184
|
-
}
|
|
3185
|
-
function markSharedDeclarations(candidates) {
|
|
3186
|
-
const groups = /* @__PURE__ */ new Map();
|
|
3187
|
-
for (const candidate of candidates) {
|
|
3188
|
-
const declaration = candidate.declaration;
|
|
3189
|
-
if (declaration?.kind !== "property") continue;
|
|
3190
|
-
const key = `${declaration.file}#${declaration.propertyName}`;
|
|
3191
|
-
groups.set(key, [...groups.get(key) ?? [], candidate]);
|
|
3192
|
-
}
|
|
3193
|
-
for (const group of groups.values()) {
|
|
3194
|
-
if (group.length < 2) continue;
|
|
3195
|
-
for (const candidate of group) {
|
|
3196
|
-
candidate.sharedWith = group.filter((other) => other !== candidate).map((other) => other.coordinate.identifier);
|
|
3197
|
-
}
|
|
3198
|
-
}
|
|
3199
|
-
}
|
|
3200
|
-
|
|
3201
|
-
// src/adapters/maven/maven-updater.ts
|
|
3202
|
-
import { readFileSync as readFileSync15, writeFileSync as writeFileSync4 } from "fs";
|
|
3203
|
-
import { join as join21, resolve as resolve12 } from "path";
|
|
3204
|
-
var MavenUpdaterAdapter = class {
|
|
3205
|
-
affectedFiles(module, _rootDir, updates) {
|
|
3206
|
-
const files = new Set(updates.map((u) => resolve12(u.declaration?.file ?? join21(module.path, "pom.xml"))));
|
|
3207
|
-
return [...files];
|
|
3208
|
-
}
|
|
3209
|
-
async applyUpdates(module, rootDir, updates, onProgress) {
|
|
3210
|
-
const project = MavenProject.load(rootDir);
|
|
3211
|
-
const byFile = /* @__PURE__ */ new Map();
|
|
3212
|
-
for (const update of updates) {
|
|
3213
|
-
if (!update.declaration) {
|
|
3214
|
-
throw new InstallError(`No version declaration known for ${update.coordinate.identifier}; rescan the module.`);
|
|
3215
|
-
}
|
|
3216
|
-
const file = resolve12(update.declaration.file);
|
|
3217
|
-
byFile.set(file, [...byFile.get(file) ?? [], update]);
|
|
3218
|
-
}
|
|
3219
|
-
const writes = [];
|
|
3220
|
-
for (const [file, fileUpdates] of byFile) {
|
|
3221
|
-
onProgress(`Preparing ${fileUpdates.length} change(s) in ${fileUpdates[0].declaration.displayPath}\u2026`);
|
|
3222
|
-
const source = readFileSync15(file, "utf8");
|
|
3223
|
-
const pom = parsePom(source, file);
|
|
3224
|
-
const chain = project.pomAt(file) ? project.chainOf(project.pomAt(file)).poms : [pom];
|
|
3225
|
-
const edits = collectEdits(pom, [pom, ...chain.slice(1)], fileUpdates);
|
|
3226
|
-
try {
|
|
3227
|
-
writes.push({ file, content: applyTextEdits(source, edits) });
|
|
3228
|
-
} catch (err) {
|
|
3229
|
-
if (err instanceof StaleEditError) throw new InstallError(`${file}: ${err.message}`);
|
|
3230
|
-
throw err;
|
|
3231
|
-
}
|
|
3232
|
-
}
|
|
3233
|
-
onProgress("Writing POM files\u2026");
|
|
3234
|
-
for (const write of writes) writeFileSync4(write.file, write.content, "utf8");
|
|
3235
|
-
return {
|
|
3236
|
-
updatedCount: updates.length,
|
|
3237
|
-
auditMessage: `Updated ${updates.length} version declaration(s) in ${writes.length} POM file(s).`,
|
|
3238
|
-
auditSeverity: "clean",
|
|
3239
|
-
integrityViolations: []
|
|
3240
|
-
};
|
|
3241
|
-
}
|
|
3242
|
-
async resync() {
|
|
3243
|
-
}
|
|
3244
|
-
};
|
|
3245
|
-
function collectEdits(pom, chain, updates) {
|
|
3246
|
-
const edits = /* @__PURE__ */ new Map();
|
|
3247
|
-
for (const update of updates) {
|
|
3248
|
-
const element = findDeclarationElement(pom, chain, update);
|
|
3249
|
-
if (!element) {
|
|
3250
|
-
throw new InstallError(
|
|
3251
|
-
`${update.declaration.displayPath}: cannot find the version of ${update.coordinate.identifier} any more; rescan the module.`
|
|
3252
|
-
);
|
|
3253
|
-
}
|
|
3254
|
-
const existing = edits.get(element);
|
|
3255
|
-
if (existing) {
|
|
3256
|
-
if (existing.replacement !== update.newRange) {
|
|
3257
|
-
throw new InstallError(
|
|
3258
|
-
`${update.coordinate.identifier} and ${existing.identifier} share ${describe(update.declaration)} but target different versions (${update.newRange} vs ${existing.replacement}).`
|
|
3259
|
-
);
|
|
3260
|
-
}
|
|
3261
|
-
continue;
|
|
3262
|
-
}
|
|
3263
|
-
edits.set(element, {
|
|
3264
|
-
element,
|
|
3265
|
-
expected: update.currentRange,
|
|
3266
|
-
replacement: update.newRange,
|
|
3267
|
-
identifier: update.coordinate.identifier
|
|
3268
|
-
});
|
|
3269
|
-
}
|
|
3270
|
-
return [...edits.values()];
|
|
3271
|
-
}
|
|
3272
|
-
function findDeclarationElement(pom, chain, update) {
|
|
3273
|
-
const declaration = update.declaration;
|
|
3274
|
-
const { group, artifact } = update.coordinate;
|
|
3275
|
-
const matches = (dep) => interpolate(dep.groupId, chain) === group && interpolate(dep.artifactId, chain) === artifact;
|
|
3276
|
-
switch (declaration.kind) {
|
|
3277
|
-
case "property":
|
|
3278
|
-
return declaration.propertyName ? pom.properties.get(declaration.propertyName)?.element : void 0;
|
|
3279
|
-
case "parent":
|
|
3280
|
-
return pom.parent?.versionElement;
|
|
3281
|
-
case "dependency":
|
|
3282
|
-
return pom.dependencies.find(matches)?.versionElement;
|
|
3283
|
-
case "dependency-management":
|
|
3284
|
-
return pom.managedDependencies.find(matches)?.versionElement;
|
|
3285
|
-
}
|
|
3286
|
-
}
|
|
3287
|
-
function describe(declaration) {
|
|
3288
|
-
return declaration.kind === "property" ? `the property \${${declaration.propertyName}} in ${declaration.displayPath}` : `a version in ${declaration.displayPath}`;
|
|
3289
|
-
}
|
|
3290
|
-
|
|
3291
|
-
// src/adapters/maven/maven-verifier.ts
|
|
3292
|
-
import { dirname as dirname9, join as join22, relative as relative9, resolve as resolve13 } from "path";
|
|
3293
|
-
var VERIFY_TIMEOUT_MS3 = 30 * 6e4;
|
|
3294
|
-
var MavenVerificationAdapter = class {
|
|
3295
|
-
async verify(module, rootDir, options = {}) {
|
|
3296
|
-
if (options.customScript) {
|
|
3297
|
-
const label2 = options.customLabel || options.customScript;
|
|
3298
|
-
const result = await runShellScript(options.customScript, { cwd: module.path, timeoutMs: VERIFY_TIMEOUT_MS3 });
|
|
3299
|
-
return result.exitCode === 0 ? { status: "clean", details: "Verification script succeeded.", label: label2 } : { status: "warn", details: failureDetails2(result), label: label2 };
|
|
3300
|
-
}
|
|
3301
|
-
const plan = planBuild(module, rootDir, options.changedFiles ?? []);
|
|
3302
|
-
const label = `Maven build (${["test-compile", ...plan.args].join(" ")})`;
|
|
3303
|
-
try {
|
|
3304
|
-
const result = await runMaven(["-q", "test-compile", ...plan.args], {
|
|
3305
|
-
cwd: plan.cwd,
|
|
3306
|
-
rootDir,
|
|
3307
|
-
timeoutMs: VERIFY_TIMEOUT_MS3
|
|
3308
|
-
});
|
|
3309
|
-
if (result.exitCode === 0) {
|
|
3310
|
-
return { status: "clean", details: "All affected modules compile, including tests.", label };
|
|
3311
|
-
}
|
|
3312
|
-
const details = result.timedOut ? "Maven build timed out." : failureDetails2(result);
|
|
3313
|
-
return { status: "warn", details, label };
|
|
3314
|
-
} catch (err) {
|
|
3315
|
-
if (err instanceof MavenUnavailableError) return { status: "warn", details: err.message, label };
|
|
3316
|
-
throw err;
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3319
|
-
};
|
|
3320
|
-
function failureDetails2(result) {
|
|
3321
|
-
const output = `${result.stdout}
|
|
3322
|
-
${result.stderr}`.trim();
|
|
3323
|
-
return output ? mavenErrors(output) : `Exited with code ${result.exitCode}.`;
|
|
3324
|
-
}
|
|
3325
|
-
function planBuild(module, rootDir, changedFiles) {
|
|
3326
|
-
const aggregatorDir = module.aggregatorDir;
|
|
3327
|
-
if (!aggregatorDir) return { cwd: module.path, args: [] };
|
|
3328
|
-
const project = MavenProject.load(rootDir);
|
|
3329
|
-
const files = changedFiles.length > 0 ? changedFiles : [join22(module.path, "pom.xml")];
|
|
3330
|
-
const selected = /* @__PURE__ */ new Set();
|
|
3331
|
-
for (const file of files) {
|
|
3332
|
-
const pom = project.pomAt(file);
|
|
3333
|
-
const dir = resolve13(dirname9(file));
|
|
3334
|
-
if (!pom || project.aggregatorDirOf(pom) !== aggregatorDir || dir === resolve13(aggregatorDir)) {
|
|
3335
|
-
return { cwd: aggregatorDir, args: [] };
|
|
3336
|
-
}
|
|
3337
|
-
selected.add(relative9(aggregatorDir, dir).replace(/\\/g, "/"));
|
|
3338
|
-
}
|
|
3339
|
-
return { cwd: aggregatorDir, args: ["-pl", [...selected].join(","), "-amd"] };
|
|
3340
|
-
}
|
|
3341
|
-
|
|
3342
|
-
// src/adapters/maven/maven-strategy.ts
|
|
3343
|
-
var MavenEcosystemStrategy = class {
|
|
3344
|
-
ecosystem = "maven";
|
|
3345
|
-
displayName = "Java / Maven";
|
|
3346
|
-
icon = "\u2615";
|
|
3347
|
-
manifestFile = "pom.xml";
|
|
3348
|
-
versioning = mavenVersionPolicy;
|
|
3349
|
-
discovery = new MavenModuleDiscoveryAdapter();
|
|
3350
|
-
registry;
|
|
3351
|
-
reader;
|
|
3352
|
-
updater = new MavenUpdaterAdapter();
|
|
3353
|
-
verifier = new MavenVerificationAdapter();
|
|
3354
|
-
/** @param plugins versions of the Maven plugins Elevate runs (default: the pinned ones) */
|
|
3355
|
-
constructor(plugins = DEFAULT_MAVEN_PLUGINS) {
|
|
3356
|
-
this.registry = new MavenRegistryAdapter(plugins);
|
|
3357
|
-
this.reader = new MavenDependencyAdapter(plugins);
|
|
3358
|
-
}
|
|
3359
|
-
getTabLabels(counts, _t) {
|
|
3360
|
-
return {
|
|
3361
|
-
all: `[1] Alle (${counts.total})`,
|
|
3362
|
-
prod: `[2] Compile (${counts.prodCount})`,
|
|
3363
|
-
dev: `[3] Test (${counts.devCount})`
|
|
3364
|
-
};
|
|
3365
|
-
}
|
|
3366
|
-
};
|
|
3367
|
-
|
|
3368
|
-
// src/domain/ecosystem-factory.ts
|
|
3369
|
-
var EcosystemFactory = class {
|
|
3370
|
-
static instances = /* @__PURE__ */ new Map();
|
|
3371
|
-
static settings = {};
|
|
3372
|
-
/**
|
|
3373
|
-
* Stores the settings and drops the cached strategies, so strategies created
|
|
3374
|
-
* afterwards use them. Without a call the defaults apply.
|
|
3375
|
-
*/
|
|
3376
|
-
static configure(settings) {
|
|
3377
|
-
this.settings = settings;
|
|
3378
|
-
this.instances.clear();
|
|
3379
|
-
}
|
|
3380
|
-
/** Returns the strategy for an ecosystem (lazily created singleton). */
|
|
3381
|
-
static getStrategy(ecosystem) {
|
|
3382
|
-
let strategy = this.instances.get(ecosystem);
|
|
3383
|
-
if (!strategy) {
|
|
3384
|
-
if (ecosystem === "maven") {
|
|
3385
|
-
strategy = new MavenEcosystemStrategy(this.settings.mavenPlugins);
|
|
3386
|
-
} else if (ecosystem === "cargo") {
|
|
3387
|
-
strategy = new CargoEcosystemStrategy();
|
|
3388
|
-
} else {
|
|
3389
|
-
strategy = new NpmEcosystemStrategy();
|
|
3390
|
-
}
|
|
3391
|
-
this.instances.set(ecosystem, strategy);
|
|
3392
|
-
}
|
|
3393
|
-
return strategy;
|
|
3394
|
-
}
|
|
3395
|
-
/** All supported ecosystems. */
|
|
3396
|
-
static getAvailableEcosystems() {
|
|
3397
|
-
return ["npm", "maven", "cargo"];
|
|
3398
|
-
}
|
|
3399
|
-
/** The ecosystem after `current`, for the dashboard's ecosystem switch. */
|
|
3400
|
-
static next(current) {
|
|
3401
|
-
const all = this.getAvailableEcosystems();
|
|
3402
|
-
return all[(all.indexOf(current) + 1) % all.length];
|
|
3403
|
-
}
|
|
3404
|
-
};
|
|
3405
|
-
|
|
3406
|
-
// src/config.ts
|
|
3407
|
-
function findRepositoryRoot(startDir) {
|
|
3408
|
-
const start = resolve14(startDir);
|
|
3409
|
-
const boundary = findCheckoutBoundary(start);
|
|
3410
|
-
let dir = start;
|
|
3411
|
-
for (; ; ) {
|
|
3412
|
-
if (existsSync16(join23(dir, "elevate.config.json"))) return dir;
|
|
3413
|
-
const parent = dirname10(dir);
|
|
3414
|
-
if (dir === boundary || parent === dir) break;
|
|
3415
|
-
dir = parent;
|
|
3416
|
-
}
|
|
3417
|
-
const proposals = EcosystemFactory.getAvailableEcosystems().map((ecosystem) => EcosystemFactory.getStrategy(ecosystem).discovery.findRoot?.(start, boundary)).filter((root) => root !== void 0);
|
|
3418
|
-
return proposals.sort((a, b) => a.length - b.length)[0] ?? start;
|
|
3419
|
-
}
|
|
3420
|
-
var ConfigError = class extends Error {
|
|
3421
|
-
constructor(message2) {
|
|
3422
|
-
super(message2);
|
|
3423
|
-
this.name = "ConfigError";
|
|
3424
|
-
}
|
|
3425
|
-
};
|
|
3426
|
-
var NO_CONTINUE = "Elevate does not continue without your internalScopes.";
|
|
3427
|
-
function stringList(file, key, source) {
|
|
3428
|
-
if (!Object.hasOwn(file, key)) return void 0;
|
|
3429
|
-
const value = file[key];
|
|
3430
|
-
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) return value;
|
|
3431
|
-
throw new ConfigError(
|
|
3432
|
-
`Invalid elevate.config.json (${source}): \`${key}\` must be a list of strings, for example ["@my-org", "com.mycompany"]. ${NO_CONTINUE}`
|
|
3433
|
-
);
|
|
3434
|
-
}
|
|
3435
|
-
var PLUGIN_VERSION = /^[0-9][A-Za-z0-9.-]{0,63}$/;
|
|
3436
|
-
function resolveMavenPlugins(value, warnings) {
|
|
3437
|
-
const plugins = { ...DEFAULT_MAVEN_PLUGINS };
|
|
3438
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return plugins;
|
|
3439
|
-
const given = value;
|
|
3440
|
-
for (const key of Object.keys(DEFAULT_MAVEN_PLUGINS)) {
|
|
3441
|
-
if (!Object.hasOwn(given, key)) continue;
|
|
3442
|
-
const version = given[key];
|
|
3443
|
-
if (typeof version === "string" && PLUGIN_VERSION.test(version)) {
|
|
3444
|
-
plugins[key] = version;
|
|
3445
|
-
} else {
|
|
3446
|
-
warnings.push(
|
|
3447
|
-
`\`mavenPlugins.${key}\` is not a valid version (${JSON.stringify(version)}); using the default ${DEFAULT_MAVEN_PLUGINS[key]}.`
|
|
3448
|
-
);
|
|
3449
|
-
}
|
|
3450
|
-
}
|
|
3451
|
-
return plugins;
|
|
3452
|
-
}
|
|
3453
|
-
function resolveConfig(rootDir, file, source = "elevate.config.json") {
|
|
3454
|
-
const deprecations = [];
|
|
3455
|
-
const warnings = [];
|
|
3456
|
-
const internal = stringList(file, "internalScopes", source);
|
|
3457
|
-
const legacy = stringList(file, "excludeScopes", source);
|
|
3458
|
-
if (legacy) {
|
|
3459
|
-
deprecations.push(
|
|
3460
|
-
internal ? "`excludeScopes` is deprecated and ignored because `internalScopes` is set; remove it from elevate.config.json." : "`excludeScopes` is deprecated; rename it to `internalScopes` in elevate.config.json."
|
|
3461
|
-
);
|
|
3462
|
-
}
|
|
3463
|
-
return {
|
|
3464
|
-
rootDir,
|
|
3465
|
-
author: typeof file.author === "string" ? file.author : void 0,
|
|
3466
|
-
internalScopes: internal ?? legacy ?? [],
|
|
3467
|
-
postUpdateScript: typeof file.postUpdateScript === "string" ? file.postUpdateScript : void 0,
|
|
3468
|
-
postUpdateLabel: typeof file.postUpdateLabel === "string" ? file.postUpdateLabel : typeof file.postUpdateScript === "string" ? file.postUpdateScript : void 0,
|
|
3469
|
-
locale: file.locale === "de" || file.locale === "en" ? file.locale : void 0,
|
|
3470
|
-
channel: file.channel === "all" ? "all" : "stable",
|
|
3471
|
-
deprecations,
|
|
3472
|
-
mavenPlugins: resolveMavenPlugins(file.mavenPlugins, warnings),
|
|
3473
|
-
warnings
|
|
3474
|
-
};
|
|
3475
|
-
}
|
|
3476
|
-
function loadConfig2(startDir = process.cwd()) {
|
|
3477
|
-
const rootDir = findRepositoryRoot(startDir);
|
|
3478
|
-
const configPath = join23(rootDir, "elevate.config.json");
|
|
3479
|
-
let file = {};
|
|
3480
|
-
if (existsSync16(configPath)) {
|
|
3481
|
-
const unreadable = (detail) => new ConfigError(
|
|
3482
|
-
`Cannot read elevate.config.json: ${detail} (${configPath}). Fix the file or remove it; ${NO_CONTINUE}`
|
|
3483
|
-
);
|
|
3484
|
-
let parsed;
|
|
3485
|
-
try {
|
|
3486
|
-
parsed = JSON.parse(readFileSync16(configPath, "utf8").replace(/^/, ""));
|
|
3487
|
-
} catch (err) {
|
|
3488
|
-
throw unreadable(err instanceof Error ? err.message : String(err));
|
|
3489
|
-
}
|
|
3490
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3491
|
-
throw unreadable("the file must contain a JSON object");
|
|
3492
|
-
}
|
|
3493
|
-
file = parsed;
|
|
3494
|
-
}
|
|
3495
|
-
return resolveConfig(rootDir, file, configPath);
|
|
3496
|
-
}
|
|
3497
|
-
|
|
3498
18
|
// src/app.tsx
|
|
3499
19
|
import { useState as useState8, useMemo as useMemo4, useCallback as useCallback5 } from "react";
|
|
3500
20
|
import { Box as Box14, useInput as useInput2, useApp } from "ink";
|
|
@@ -3518,7 +38,9 @@ var de = {
|
|
|
3518
38
|
tabs: {
|
|
3519
39
|
all: (count) => `[1] Alle (${count})`,
|
|
3520
40
|
dependencies: (count) => `[2] Dependencies (${count})`,
|
|
3521
|
-
devDependencies: (count) => `[3] DevDependencies (${count})
|
|
41
|
+
devDependencies: (count) => `[3] DevDependencies (${count})`,
|
|
42
|
+
main: (count) => `[2] Haupt (${count})`,
|
|
43
|
+
testAndPlugins: (count) => `[3] Test & Plugins (${count})`
|
|
3522
44
|
},
|
|
3523
45
|
list: {
|
|
3524
46
|
scanning: "\u23F3 Pr\xFCfe Registry auf verf\xFCgbare Updates\u2026",
|
|
@@ -3534,7 +56,8 @@ var de = {
|
|
|
3534
56
|
"declaration-mismatch": "Ort der Versionsangabe ist nicht eindeutig",
|
|
3535
57
|
"invalid-name": "ung\xFCltiger Paketname",
|
|
3536
58
|
"not-linked": "Workspace-Paket, das pnpm ohne das workspace:-Protokoll nicht verlinkt",
|
|
3537
|
-
"catalog-unresolved": "Catalog-Eintrag konnte nicht aufgel\xF6st werden"
|
|
59
|
+
"catalog-unresolved": "Catalog-Eintrag konnte nicht aufgel\xF6st werden",
|
|
60
|
+
"version-constraint": "Version ist ein Selektor oder eine Einschr\xE4nkung, keine feste Version"
|
|
3538
61
|
})[reason]
|
|
3539
62
|
},
|
|
3540
63
|
badges: {
|
|
@@ -3630,7 +153,9 @@ var en = {
|
|
|
3630
153
|
tabs: {
|
|
3631
154
|
all: (count) => `[1] All (${count})`,
|
|
3632
155
|
dependencies: (count) => `[2] Dependencies (${count})`,
|
|
3633
|
-
devDependencies: (count) => `[3] DevDependencies (${count})
|
|
156
|
+
devDependencies: (count) => `[3] DevDependencies (${count})`,
|
|
157
|
+
main: (count) => `[2] Main (${count})`,
|
|
158
|
+
testAndPlugins: (count) => `[3] Test & plugins (${count})`
|
|
3634
159
|
},
|
|
3635
160
|
list: {
|
|
3636
161
|
scanning: "\u23F3 Checking registry for available updates\u2026",
|
|
@@ -3646,7 +171,8 @@ var en = {
|
|
|
3646
171
|
"declaration-mismatch": "version location is ambiguous",
|
|
3647
172
|
"invalid-name": "invalid package name",
|
|
3648
173
|
"not-linked": "workspace package that pnpm does not link without the workspace: protocol",
|
|
3649
|
-
"catalog-unresolved": "catalog entry could not be resolved"
|
|
174
|
+
"catalog-unresolved": "catalog entry could not be resolved",
|
|
175
|
+
"version-constraint": "version is a selector or constraint, not a plain version"
|
|
3650
176
|
})[reason]
|
|
3651
177
|
},
|
|
3652
178
|
badges: {
|
|
@@ -3795,6 +321,15 @@ async function scanModules(strategy, modules, context) {
|
|
|
3795
321
|
for (const module of modules) results.set(module, await scanModule(strategy, module, context));
|
|
3796
322
|
return results;
|
|
3797
323
|
}
|
|
324
|
+
function declarationToJson(declaration) {
|
|
325
|
+
return {
|
|
326
|
+
file: declaration.displayPath,
|
|
327
|
+
kind: declaration.kind,
|
|
328
|
+
property: declaration.propertyName,
|
|
329
|
+
catalog: declaration.catalogName,
|
|
330
|
+
entry: declaration.entry
|
|
331
|
+
};
|
|
332
|
+
}
|
|
3798
333
|
function candidateToJson(u) {
|
|
3799
334
|
return {
|
|
3800
335
|
identifier: u.coordinate.identifier,
|
|
@@ -3808,12 +343,8 @@ function candidateToJson(u) {
|
|
|
3808
343
|
scope: u.scope,
|
|
3809
344
|
isPreRelease: u.isPreRelease ?? false,
|
|
3810
345
|
preReleaseTag: u.preReleaseTag,
|
|
3811
|
-
declaredIn: u.declaration ?
|
|
3812
|
-
|
|
3813
|
-
kind: u.declaration.kind,
|
|
3814
|
-
property: u.declaration.propertyName,
|
|
3815
|
-
catalog: u.declaration.catalogName
|
|
3816
|
-
} : void 0,
|
|
346
|
+
declaredIn: u.declaration ? declarationToJson(u.declaration) : void 0,
|
|
347
|
+
alsoDeclaredIn: u.declarations && u.declarations.length > 1 ? u.declarations.slice(1).map(declarationToJson) : void 0,
|
|
3817
348
|
sharedWith: u.sharedWith
|
|
3818
349
|
};
|
|
3819
350
|
}
|
|
@@ -3977,24 +508,24 @@ function usePackages(strategy, module, modules, config, channel = "stable") {
|
|
|
3977
508
|
import { useState as useState4, useCallback as useCallback4 } from "react";
|
|
3978
509
|
|
|
3979
510
|
// src/application/update-workflow.ts
|
|
3980
|
-
import { existsSync as
|
|
3981
|
-
import { relative
|
|
511
|
+
import { existsSync as existsSync2 } from "fs";
|
|
512
|
+
import { relative } from "path";
|
|
3982
513
|
|
|
3983
514
|
// src/adapters/shared/file-snapshot.ts
|
|
3984
|
-
import { existsSync
|
|
515
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
3985
516
|
var FileSnapshot = class _FileSnapshot {
|
|
3986
|
-
constructor(
|
|
3987
|
-
this.entries =
|
|
517
|
+
constructor(entries) {
|
|
518
|
+
this.entries = entries;
|
|
3988
519
|
}
|
|
3989
520
|
entries;
|
|
3990
521
|
/** Records the current content (or absence) of each file. */
|
|
3991
522
|
static capture(files) {
|
|
3992
|
-
const
|
|
523
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3993
524
|
for (const file of files) {
|
|
3994
|
-
if (
|
|
3995
|
-
|
|
525
|
+
if (entries.has(file)) continue;
|
|
526
|
+
entries.set(file, existsSync(file) ? readFileSync(file) : null);
|
|
3996
527
|
}
|
|
3997
|
-
return new _FileSnapshot(
|
|
528
|
+
return new _FileSnapshot(entries);
|
|
3998
529
|
}
|
|
3999
530
|
/** The files covered by this snapshot. */
|
|
4000
531
|
get files() {
|
|
@@ -4004,18 +535,18 @@ var FileSnapshot = class _FileSnapshot {
|
|
|
4004
535
|
changedFiles() {
|
|
4005
536
|
return this.files.filter((file) => {
|
|
4006
537
|
const before = this.entries.get(file) ?? null;
|
|
4007
|
-
const exists =
|
|
538
|
+
const exists = existsSync(file);
|
|
4008
539
|
if (before === null) return exists;
|
|
4009
|
-
return !exists || !
|
|
540
|
+
return !exists || !readFileSync(file).equals(before);
|
|
4010
541
|
});
|
|
4011
542
|
}
|
|
4012
543
|
/** Writes every file back to its captured state; removes files that did not exist. */
|
|
4013
544
|
restore() {
|
|
4014
545
|
for (const [file, content] of this.entries) {
|
|
4015
546
|
if (content === null) {
|
|
4016
|
-
|
|
547
|
+
rmSync(file, { force: true });
|
|
4017
548
|
} else {
|
|
4018
|
-
|
|
549
|
+
writeFileSync(file, content);
|
|
4019
550
|
}
|
|
4020
551
|
}
|
|
4021
552
|
}
|
|
@@ -4054,7 +585,7 @@ Restoring the installed state failed as well: ${message(err)}`;
|
|
|
4054
585
|
let baseline;
|
|
4055
586
|
if (!options.skipVerification) {
|
|
4056
587
|
progress("Running verification on the current state\u2026");
|
|
4057
|
-
baseline = await verify(snapshot.files.filter((file) =>
|
|
588
|
+
baseline = await verify(snapshot.files.filter((file) => existsSync2(file)));
|
|
4058
589
|
}
|
|
4059
590
|
let applied;
|
|
4060
591
|
try {
|
|
@@ -4100,7 +631,7 @@ function message(err) {
|
|
|
4100
631
|
return err instanceof Error ? err.message : String(err);
|
|
4101
632
|
}
|
|
4102
633
|
function displayPaths(files, rootDir) {
|
|
4103
|
-
return files.map((file) =>
|
|
634
|
+
return files.map((file) => relative(rootDir, file).replace(/\\/g, "/"));
|
|
4104
635
|
}
|
|
4105
636
|
|
|
4106
637
|
// src/hooks/use-updater.ts
|
|
@@ -4581,7 +1112,7 @@ var ChickenMascot = ({ state = "idle", t, ecosystem = "npm" }) => {
|
|
|
4581
1112
|
chickFace = cur.face;
|
|
4582
1113
|
wingLeft = cur.l;
|
|
4583
1114
|
wingRight = cur.r;
|
|
4584
|
-
const chirpsPool = ecosystem === "maven" ? [...t.mascot.idleChirps, ...t.mascot.mavenChirps] : ecosystem === "cargo" ? [...t.mascot.idleChirps, ...t.mascot.cargoChirps] : t.mascot.idleChirps;
|
|
1115
|
+
const chirpsPool = ecosystem === "maven" || ecosystem === "gradle" ? [...t.mascot.idleChirps, ...t.mascot.mavenChirps] : ecosystem === "cargo" ? [...t.mascot.idleChirps, ...t.mascot.cargoChirps] : t.mascot.idleChirps;
|
|
4585
1116
|
currentChirp = chirpsPool[chirpIndex % chirpsPool.length];
|
|
4586
1117
|
}
|
|
4587
1118
|
return /* @__PURE__ */ jsxs7(Box7, { alignItems: "center", children: [
|
|
@@ -4741,7 +1272,7 @@ var PublicLookupRefusedError = class extends Error {
|
|
|
4741
1272
|
}
|
|
4742
1273
|
};
|
|
4743
1274
|
function coordinateFromIdentifier(identifier, ecosystem) {
|
|
4744
|
-
if (ecosystem === "maven") {
|
|
1275
|
+
if (ecosystem === "maven" || ecosystem === "gradle") {
|
|
4745
1276
|
const [group, artifact] = identifier.split(":");
|
|
4746
1277
|
return { identifier, group, artifact: artifact || group, ecosystem };
|
|
4747
1278
|
}
|
|
@@ -5313,7 +1844,7 @@ SUBCOMMANDS:
|
|
|
5313
1844
|
mcp Start stdio-based MCP server
|
|
5314
1845
|
|
|
5315
1846
|
FLAGS:
|
|
5316
|
-
-e, --ecosystem <
|
|
1847
|
+
-e, --ecosystem <name> Target ecosystem: npm, maven, cargo or gradle (default: npm)
|
|
5317
1848
|
-m, --module <name|path> Target module ID or relative path (default: root)
|
|
5318
1849
|
-c, --channel <stable|all> Release channel (default: stable)
|
|
5319
1850
|
-p, --packages <pkg1,pkg2@1.2.3> Comma-separated list of packages to update
|
|
@@ -5329,6 +1860,7 @@ FLAGS:
|
|
|
5329
1860
|
EXAMPLES:
|
|
5330
1861
|
elevate scan --json
|
|
5331
1862
|
elevate scan --ecosystem=maven --channel=stable --all-modules --json
|
|
1863
|
+
elevate scan --ecosystem=gradle --json
|
|
5332
1864
|
elevate versions chalk --ecosystem=npm --json
|
|
5333
1865
|
elevate update --module=apps/e2e-cockpit --packages=chalk@5.6.2 --json
|
|
5334
1866
|
elevate update --ecosystem=npm --all --allow-major --dry-run
|
|
@@ -5380,6 +1912,10 @@ function describeSkip(skipped) {
|
|
|
5380
1912
|
return `catalog entry could not be resolved${detail}`;
|
|
5381
1913
|
case "incompatible-rust-version":
|
|
5382
1914
|
return `newer versions need a newer Rust than the project's rust-version${detail}`;
|
|
1915
|
+
case "version-constraint":
|
|
1916
|
+
return `version is a selector or constraint, not a plain version${detail}`;
|
|
1917
|
+
case "locked-version-yanked":
|
|
1918
|
+
return `Cargo.lock pins a yanked release and no allowed release can replace it${detail}`;
|
|
5383
1919
|
}
|
|
5384
1920
|
}
|
|
5385
1921
|
|
|
@@ -5464,13 +2000,14 @@ async function handleScanCommand(options, config) {
|
|
|
5464
2000
|
`);
|
|
5465
2001
|
continue;
|
|
5466
2002
|
}
|
|
5467
|
-
const
|
|
2003
|
+
const prefix = module.isRoot ? "" : `${module.relPath}/`;
|
|
2004
|
+
const ownFiles = module.ecosystem === "gradle" ? [`${prefix}build.gradle`, `${prefix}build.gradle.kts`] : [`${prefix}pom.xml`];
|
|
5468
2005
|
if (result.candidates.length === 0) console.log(" \u2728 All dependencies are up to date.");
|
|
5469
2006
|
for (const u of result.candidates) {
|
|
5470
2007
|
const diffTag = `[${u.diff.toUpperCase()}]`.padEnd(9);
|
|
5471
2008
|
const actionTag = u.action === "align" ? "[ALIGN] " : u.action === "lock" ? "[LOCK] " : u.origin.kind === "private" ? "[INTERNAL] " : "";
|
|
5472
2009
|
const preBadge = u.isPreRelease ? `[${u.preReleaseTag || "PRE"}] ` : "";
|
|
5473
|
-
const where = u.declaration && u.declaration.displayPath
|
|
2010
|
+
const where = u.declaration && !ownFiles.includes(u.declaration.displayPath) ? ` (in ${u.declaration.displayPath}${u.declaration.propertyName ? ` \${${u.declaration.propertyName}}` : ""}${u.declaration.entry ? ` ${u.declaration.entry}` : ""})` : "";
|
|
5474
2011
|
console.log(
|
|
5475
2012
|
` \u2022 ${diffTag} ${actionTag}${preBadge}${u.coordinate.identifier.padEnd(36)} ${u.currentRange.padEnd(12)} \u2794 ${u.latest}${where}`
|
|
5476
2013
|
);
|
|
@@ -5539,7 +2076,7 @@ async function handleVersionsCommand(options, config) {
|
|
|
5539
2076
|
}
|
|
5540
2077
|
|
|
5541
2078
|
// src/application/update-selection.ts
|
|
5542
|
-
import
|
|
2079
|
+
import semver from "semver";
|
|
5543
2080
|
var UpdateSelectionError = class extends Error {
|
|
5544
2081
|
constructor(message2) {
|
|
5545
2082
|
super(message2);
|
|
@@ -5616,7 +2153,7 @@ function retarget(found, target, options) {
|
|
|
5616
2153
|
};
|
|
5617
2154
|
}
|
|
5618
2155
|
function manualNpmCandidate(identifier, target, options) {
|
|
5619
|
-
if (!
|
|
2156
|
+
if (!semver.valid(target)) {
|
|
5620
2157
|
throw new UpdateSelectionError(`'${target}' is not a valid version for '${identifier}'.`);
|
|
5621
2158
|
}
|
|
5622
2159
|
return {
|
|
@@ -5788,9 +2325,9 @@ function parsePackageArgument(item) {
|
|
|
5788
2325
|
}
|
|
5789
2326
|
|
|
5790
2327
|
// src/cli/command-init.ts
|
|
5791
|
-
import { existsSync as
|
|
5792
|
-
import { homedir
|
|
5793
|
-
import { join
|
|
2328
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
|
|
2329
|
+
import { homedir, platform } from "os";
|
|
2330
|
+
import { join, resolve } from "path";
|
|
5794
2331
|
function mcpServerEntry() {
|
|
5795
2332
|
return {
|
|
5796
2333
|
command: "npx",
|
|
@@ -5803,7 +2340,7 @@ var ELEVATE_SKILL_CONTENT = `# Elevate \u2014 Dependency Management Agent Skill
|
|
|
5803
2340
|
Use the Elevate MCP tools whenever the task involves:
|
|
5804
2341
|
- Scanning or auditing dependencies for available updates
|
|
5805
2342
|
- Updating one or more packages to specific or latest versions
|
|
5806
|
-
- Listing available versions of a package from npm or Maven
|
|
2343
|
+
- Listing available versions of a package from npm registries or Maven and Gradle repositories
|
|
5807
2344
|
- Discovering monorepo modules and their dependency structure
|
|
5808
2345
|
- Any dependency-related operation in a monorepo
|
|
5809
2346
|
|
|
@@ -5833,10 +2370,10 @@ automatic rollback.
|
|
|
5833
2370
|
requested). A failed install or verification is rolled back automatically.
|
|
5834
2371
|
4. \`elevate_verify\` \u2014 Verify build integrity when needed
|
|
5835
2372
|
`;
|
|
5836
|
-
var home =
|
|
2373
|
+
var home = homedir();
|
|
5837
2374
|
var isWin = platform() === "win32";
|
|
5838
2375
|
function appData() {
|
|
5839
|
-
return process.env.APPDATA ||
|
|
2376
|
+
return process.env.APPDATA || join(home, "AppData", "Roaming");
|
|
5840
2377
|
}
|
|
5841
2378
|
function detectCursor(cwd) {
|
|
5842
2379
|
const env = {
|
|
@@ -5848,25 +2385,25 @@ function detectCursor(cwd) {
|
|
|
5848
2385
|
mcpConfigured: false,
|
|
5849
2386
|
skillConfigured: false
|
|
5850
2387
|
};
|
|
5851
|
-
const workspaceCursorDir =
|
|
5852
|
-
const globalCursorDir =
|
|
5853
|
-
if (
|
|
2388
|
+
const workspaceCursorDir = join(cwd, ".cursor");
|
|
2389
|
+
const globalCursorDir = join(home, ".cursor");
|
|
2390
|
+
if (existsSync3(workspaceCursorDir) || existsSync3(globalCursorDir)) {
|
|
5854
2391
|
env.detected = true;
|
|
5855
|
-
env.reason =
|
|
2392
|
+
env.reason = existsSync3(workspaceCursorDir) ? `Found .cursor/ in workspace` : `Found ~/.cursor/ global config`;
|
|
5856
2393
|
}
|
|
5857
|
-
const mcpPath =
|
|
5858
|
-
if (
|
|
2394
|
+
const mcpPath = join(cwd, ".cursor", "mcp.json");
|
|
2395
|
+
if (existsSync3(mcpPath)) {
|
|
5859
2396
|
try {
|
|
5860
|
-
const content = JSON.parse(
|
|
2397
|
+
const content = JSON.parse(readFileSync2(mcpPath, "utf8"));
|
|
5861
2398
|
if (content?.mcpServers?.elevate) {
|
|
5862
2399
|
env.mcpConfigured = true;
|
|
5863
2400
|
}
|
|
5864
2401
|
} catch {
|
|
5865
2402
|
}
|
|
5866
2403
|
}
|
|
5867
|
-
const rulesPath =
|
|
5868
|
-
if (
|
|
5869
|
-
const content =
|
|
2404
|
+
const rulesPath = join(cwd, ".cursorrules");
|
|
2405
|
+
if (existsSync3(rulesPath)) {
|
|
2406
|
+
const content = readFileSync2(rulesPath, "utf8");
|
|
5870
2407
|
if (content.includes("elevate") || content.includes("Elevate")) {
|
|
5871
2408
|
env.skillConfigured = true;
|
|
5872
2409
|
}
|
|
@@ -5883,12 +2420,12 @@ function detectClaudeDesktop() {
|
|
|
5883
2420
|
mcpConfigured: false,
|
|
5884
2421
|
skillConfigured: false
|
|
5885
2422
|
};
|
|
5886
|
-
const configPath = isWin ?
|
|
5887
|
-
if (
|
|
2423
|
+
const configPath = isWin ? join(appData(), "Claude", "claude_desktop_config.json") : join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
2424
|
+
if (existsSync3(configPath)) {
|
|
5888
2425
|
env.detected = true;
|
|
5889
2426
|
env.reason = `Found ${configPath}`;
|
|
5890
2427
|
try {
|
|
5891
|
-
const content = JSON.parse(
|
|
2428
|
+
const content = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
5892
2429
|
if (content?.mcpServers?.elevate) {
|
|
5893
2430
|
env.mcpConfigured = true;
|
|
5894
2431
|
}
|
|
@@ -5907,23 +2444,23 @@ function detectClaudeCode(cwd) {
|
|
|
5907
2444
|
mcpConfigured: false,
|
|
5908
2445
|
skillConfigured: false
|
|
5909
2446
|
};
|
|
5910
|
-
const mcpJsonPath =
|
|
5911
|
-
const claudeMdPath =
|
|
5912
|
-
if (
|
|
2447
|
+
const mcpJsonPath = join(cwd, ".mcp.json");
|
|
2448
|
+
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
2449
|
+
if (existsSync3(mcpJsonPath) || existsSync3(claudeMdPath)) {
|
|
5913
2450
|
env.detected = true;
|
|
5914
|
-
env.reason =
|
|
2451
|
+
env.reason = existsSync3(mcpJsonPath) ? `Found .mcp.json in workspace` : `Found CLAUDE.md in workspace`;
|
|
5915
2452
|
}
|
|
5916
|
-
if (
|
|
2453
|
+
if (existsSync3(mcpJsonPath)) {
|
|
5917
2454
|
try {
|
|
5918
|
-
const content = JSON.parse(
|
|
2455
|
+
const content = JSON.parse(readFileSync2(mcpJsonPath, "utf8"));
|
|
5919
2456
|
if (content?.mcpServers?.elevate) {
|
|
5920
2457
|
env.mcpConfigured = true;
|
|
5921
2458
|
}
|
|
5922
2459
|
} catch {
|
|
5923
2460
|
}
|
|
5924
2461
|
}
|
|
5925
|
-
if (
|
|
5926
|
-
const content =
|
|
2462
|
+
if (existsSync3(claudeMdPath)) {
|
|
2463
|
+
const content = readFileSync2(claudeMdPath, "utf8");
|
|
5927
2464
|
if (content.includes("elevate") || content.includes("Elevate")) {
|
|
5928
2465
|
env.skillConfigured = true;
|
|
5929
2466
|
}
|
|
@@ -5940,20 +2477,20 @@ function detectAntigravity(cwd) {
|
|
|
5940
2477
|
mcpConfigured: false,
|
|
5941
2478
|
skillConfigured: false
|
|
5942
2479
|
};
|
|
5943
|
-
const globalGeminiDir =
|
|
5944
|
-
const workspaceAgentsDir =
|
|
5945
|
-
if (
|
|
2480
|
+
const globalGeminiDir = join(home, ".gemini");
|
|
2481
|
+
const workspaceAgentsDir = join(cwd, ".agents");
|
|
2482
|
+
if (existsSync3(globalGeminiDir) || existsSync3(workspaceAgentsDir)) {
|
|
5946
2483
|
env.detected = true;
|
|
5947
|
-
env.reason =
|
|
2484
|
+
env.reason = existsSync3(workspaceAgentsDir) ? `Found .agents/ in workspace` : `Found ~/.gemini/ global config`;
|
|
5948
2485
|
}
|
|
5949
2486
|
const mcpPaths = [
|
|
5950
|
-
|
|
5951
|
-
|
|
2487
|
+
join(cwd, ".agents", "mcp_config.json"),
|
|
2488
|
+
join(home, ".gemini", "config", "mcp_config.json")
|
|
5952
2489
|
];
|
|
5953
2490
|
for (const p of mcpPaths) {
|
|
5954
|
-
if (
|
|
2491
|
+
if (existsSync3(p)) {
|
|
5955
2492
|
try {
|
|
5956
|
-
const content = JSON.parse(
|
|
2493
|
+
const content = JSON.parse(readFileSync2(p, "utf8"));
|
|
5957
2494
|
if (content?.mcpServers?.elevate) {
|
|
5958
2495
|
env.mcpConfigured = true;
|
|
5959
2496
|
break;
|
|
@@ -5974,24 +2511,24 @@ function detectVSCode(cwd) {
|
|
|
5974
2511
|
mcpConfigured: false,
|
|
5975
2512
|
skillConfigured: false
|
|
5976
2513
|
};
|
|
5977
|
-
const vscodeDir =
|
|
5978
|
-
if (
|
|
2514
|
+
const vscodeDir = join(cwd, ".vscode");
|
|
2515
|
+
if (existsSync3(vscodeDir)) {
|
|
5979
2516
|
env.detected = true;
|
|
5980
2517
|
env.reason = `Found .vscode/ in workspace`;
|
|
5981
2518
|
}
|
|
5982
|
-
const mcpPath =
|
|
5983
|
-
if (
|
|
2519
|
+
const mcpPath = join(cwd, ".vscode", "mcp.json");
|
|
2520
|
+
if (existsSync3(mcpPath)) {
|
|
5984
2521
|
try {
|
|
5985
|
-
const content = JSON.parse(
|
|
2522
|
+
const content = JSON.parse(readFileSync2(mcpPath, "utf8"));
|
|
5986
2523
|
if (content?.servers?.elevate) {
|
|
5987
2524
|
env.mcpConfigured = true;
|
|
5988
2525
|
}
|
|
5989
2526
|
} catch {
|
|
5990
2527
|
}
|
|
5991
2528
|
}
|
|
5992
|
-
const copilotInstructions =
|
|
5993
|
-
if (
|
|
5994
|
-
const content =
|
|
2529
|
+
const copilotInstructions = join(cwd, ".github", "copilot-instructions.md");
|
|
2530
|
+
if (existsSync3(copilotInstructions)) {
|
|
2531
|
+
const content = readFileSync2(copilotInstructions, "utf8");
|
|
5995
2532
|
if (content.includes("elevate") || content.includes("Elevate")) {
|
|
5996
2533
|
env.skillConfigured = true;
|
|
5997
2534
|
}
|
|
@@ -6008,14 +2545,14 @@ function detectWindsurf() {
|
|
|
6008
2545
|
mcpConfigured: false,
|
|
6009
2546
|
skillConfigured: false
|
|
6010
2547
|
};
|
|
6011
|
-
const configPath = isWin ?
|
|
6012
|
-
const altPath =
|
|
6013
|
-
if (
|
|
2548
|
+
const configPath = isWin ? join(appData(), "Codeium", "Windsurf", "mcp_config.json") : join(home, ".codeium", "windsurf", "mcp_config.json");
|
|
2549
|
+
const altPath = join(home, ".codeium", "windsurf", "mcp_config.json");
|
|
2550
|
+
if (existsSync3(configPath) || existsSync3(altPath)) {
|
|
6014
2551
|
env.detected = true;
|
|
6015
2552
|
env.reason = `Found Windsurf config`;
|
|
6016
|
-
const path =
|
|
2553
|
+
const path = existsSync3(configPath) ? configPath : altPath;
|
|
6017
2554
|
try {
|
|
6018
|
-
const content = JSON.parse(
|
|
2555
|
+
const content = JSON.parse(readFileSync2(path, "utf8"));
|
|
6019
2556
|
if (content?.mcpServers?.elevate) {
|
|
6020
2557
|
env.mcpConfigured = true;
|
|
6021
2558
|
}
|
|
@@ -6026,9 +2563,9 @@ function detectWindsurf() {
|
|
|
6026
2563
|
}
|
|
6027
2564
|
function mergeJsonMcpServer(filePath, serverKey, topLevelKey) {
|
|
6028
2565
|
let existing = {};
|
|
6029
|
-
if (
|
|
2566
|
+
if (existsSync3(filePath)) {
|
|
6030
2567
|
try {
|
|
6031
|
-
existing = JSON.parse(
|
|
2568
|
+
existing = JSON.parse(readFileSync2(filePath, "utf8"));
|
|
6032
2569
|
} catch {
|
|
6033
2570
|
existing = {};
|
|
6034
2571
|
}
|
|
@@ -6040,69 +2577,69 @@ function mergeJsonMcpServer(filePath, serverKey, topLevelKey) {
|
|
|
6040
2577
|
servers[serverKey] = mcpServerEntry();
|
|
6041
2578
|
existing[topLevelKey] = servers;
|
|
6042
2579
|
const dir = filePath.substring(0, filePath.lastIndexOf(isWin ? "\\" : "/"));
|
|
6043
|
-
if (dir && !
|
|
6044
|
-
|
|
2580
|
+
if (dir && !existsSync3(dir)) {
|
|
2581
|
+
mkdirSync(dir, { recursive: true });
|
|
6045
2582
|
}
|
|
6046
|
-
|
|
2583
|
+
writeFileSync2(filePath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
6047
2584
|
return { written: true, message: `${filePath}: elevate MCP server registered` };
|
|
6048
2585
|
}
|
|
6049
2586
|
function appendSkillToFile(filePath, content, marker) {
|
|
6050
|
-
if (
|
|
6051
|
-
const existing =
|
|
2587
|
+
if (existsSync3(filePath)) {
|
|
2588
|
+
const existing = readFileSync2(filePath, "utf8");
|
|
6052
2589
|
if (existing.includes(marker)) {
|
|
6053
2590
|
return { written: false, message: `${filePath}: skill already present` };
|
|
6054
2591
|
}
|
|
6055
|
-
|
|
2592
|
+
writeFileSync2(filePath, existing + "\n\n" + content, "utf8");
|
|
6056
2593
|
return { written: true, message: `${filePath}: skill appended` };
|
|
6057
2594
|
}
|
|
6058
2595
|
const dir = filePath.substring(0, filePath.lastIndexOf(isWin ? "\\" : "/"));
|
|
6059
|
-
if (dir && !
|
|
6060
|
-
|
|
2596
|
+
if (dir && !existsSync3(dir)) {
|
|
2597
|
+
mkdirSync(dir, { recursive: true });
|
|
6061
2598
|
}
|
|
6062
|
-
|
|
2599
|
+
writeFileSync2(filePath, content, "utf8");
|
|
6063
2600
|
return { written: true, message: `${filePath}: skill created` };
|
|
6064
2601
|
}
|
|
6065
2602
|
function configureCursor(cwd, result) {
|
|
6066
|
-
const mcpPath =
|
|
2603
|
+
const mcpPath = join(cwd, ".cursor", "mcp.json");
|
|
6067
2604
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
6068
2605
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Cursor MCP: ${mcp.message}`);
|
|
6069
|
-
const rulesPath =
|
|
2606
|
+
const rulesPath = join(cwd, ".cursorrules");
|
|
6070
2607
|
const skill = appendSkillToFile(rulesPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
|
|
6071
2608
|
result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Cursor Rules: ${skill.message}`);
|
|
6072
2609
|
}
|
|
6073
2610
|
function configureClaudeDesktop(result) {
|
|
6074
|
-
const configPath = isWin ?
|
|
2611
|
+
const configPath = isWin ? join(appData(), "Claude", "claude_desktop_config.json") : join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
6075
2612
|
const mcp = mergeJsonMcpServer(configPath, "elevate", "mcpServers");
|
|
6076
2613
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Claude Desktop MCP: ${mcp.message}`);
|
|
6077
2614
|
}
|
|
6078
2615
|
function configureClaudeCode(cwd, result) {
|
|
6079
|
-
const mcpPath =
|
|
2616
|
+
const mcpPath = join(cwd, ".mcp.json");
|
|
6080
2617
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
6081
2618
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Claude Code MCP: ${mcp.message}`);
|
|
6082
|
-
const claudeMdPath =
|
|
2619
|
+
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
6083
2620
|
const skill = appendSkillToFile(claudeMdPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
|
|
6084
2621
|
result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Claude Code Skill: ${skill.message}`);
|
|
6085
2622
|
}
|
|
6086
2623
|
function configureAntigravity(cwd, result) {
|
|
6087
|
-
const mcpPath =
|
|
2624
|
+
const mcpPath = join(cwd, ".agents", "mcp_config.json");
|
|
6088
2625
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
6089
2626
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Antigravity MCP: ${mcp.message}`);
|
|
6090
2627
|
}
|
|
6091
2628
|
function configureVSCode(cwd, result) {
|
|
6092
|
-
const mcpPath =
|
|
2629
|
+
const mcpPath = join(cwd, ".vscode", "mcp.json");
|
|
6093
2630
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "servers");
|
|
6094
2631
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} VS Code MCP: ${mcp.message}`);
|
|
6095
|
-
const instructionsPath =
|
|
2632
|
+
const instructionsPath = join(cwd, ".github", "copilot-instructions.md");
|
|
6096
2633
|
const skill = appendSkillToFile(instructionsPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
|
|
6097
2634
|
result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Copilot Instructions: ${skill.message}`);
|
|
6098
2635
|
}
|
|
6099
2636
|
function configureWindsurf(result) {
|
|
6100
|
-
const configPath = isWin ?
|
|
2637
|
+
const configPath = isWin ? join(appData(), "Codeium", "Windsurf", "mcp_config.json") : join(home, ".codeium", "windsurf", "mcp_config.json");
|
|
6101
2638
|
const mcp = mergeJsonMcpServer(configPath, "elevate", "mcpServers");
|
|
6102
2639
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Windsurf MCP: ${mcp.message}`);
|
|
6103
2640
|
}
|
|
6104
2641
|
async function handleInitCommand(options) {
|
|
6105
|
-
const cwd =
|
|
2642
|
+
const cwd = resolve(process.cwd());
|
|
6106
2643
|
const result = {
|
|
6107
2644
|
cwd,
|
|
6108
2645
|
environments: [],
|
|
@@ -6130,7 +2667,7 @@ async function handleInitCommand(options) {
|
|
|
6130
2667
|
console.log(` Creating portable .mcp.json for broad compatibility.
|
|
6131
2668
|
`);
|
|
6132
2669
|
}
|
|
6133
|
-
const mcpPath =
|
|
2670
|
+
const mcpPath = join(cwd, ".mcp.json");
|
|
6134
2671
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
6135
2672
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Generic MCP: ${mcp.message}`);
|
|
6136
2673
|
} else {
|
|
@@ -6217,7 +2754,7 @@ async function startMcpServer(config) {
|
|
|
6217
2754
|
tools: [
|
|
6218
2755
|
{
|
|
6219
2756
|
name: "elevate_discover_modules",
|
|
6220
|
-
description: "Discovers all repository modules and workspaces for Node/npm (package.json), Java/Maven (pom.xml)
|
|
2757
|
+
description: "Discovers all repository modules and workspaces for Node/npm (package.json), Java/Maven (pom.xml), Rust/Cargo (Cargo.toml) or Java/Gradle (build.gradle). Gradle modules also report the kind of their build (root, included, buildSrc).",
|
|
6221
2758
|
inputSchema: {
|
|
6222
2759
|
type: "object",
|
|
6223
2760
|
properties: {
|
|
@@ -6257,7 +2794,7 @@ async function startMcpServer(config) {
|
|
|
6257
2794
|
ecosystem: ECOSYSTEM_PROPERTY,
|
|
6258
2795
|
identifier: {
|
|
6259
2796
|
type: "string",
|
|
6260
|
-
description: "Package identifier (npm: chalk, maven: org.slf4j:slf4j-api, cargo: serde)"
|
|
2797
|
+
description: "Package identifier (npm: chalk, maven/gradle: org.slf4j:slf4j-api, gradle plugin: org.springframework.boot, cargo: serde)"
|
|
6261
2798
|
}
|
|
6262
2799
|
},
|
|
6263
2800
|
required: ["identifier"]
|
|
@@ -6350,7 +2887,8 @@ async function startMcpServer(config) {
|
|
|
6350
2887
|
name: m.name,
|
|
6351
2888
|
relPath: m.relPath,
|
|
6352
2889
|
isRoot: m.isRoot,
|
|
6353
|
-
version: m.version
|
|
2890
|
+
version: m.version,
|
|
2891
|
+
...m.buildKind ? { buildKind: m.buildKind } : {}
|
|
6354
2892
|
}))
|
|
6355
2893
|
});
|
|
6356
2894
|
}
|
|
@@ -6465,7 +3003,7 @@ import { jsx as jsx15 } from "react/jsx-runtime";
|
|
|
6465
3003
|
async function main() {
|
|
6466
3004
|
let config;
|
|
6467
3005
|
try {
|
|
6468
|
-
config =
|
|
3006
|
+
config = loadConfig();
|
|
6469
3007
|
} catch (err) {
|
|
6470
3008
|
if (!(err instanceof ConfigError)) throw err;
|
|
6471
3009
|
process.stderr.write(`\u274C ${err.message}
|
|
@@ -6514,7 +3052,8 @@ async function main() {
|
|
|
6514
3052
|
render(/* @__PURE__ */ jsx15(App, { config }));
|
|
6515
3053
|
}
|
|
6516
3054
|
main().catch((err) => {
|
|
6517
|
-
|
|
3055
|
+
const detail = err instanceof Error ? process.env.ELEVATE_DEBUG ? err.stack ?? err.message : `${err.name}: ${err.message}` : String(err);
|
|
3056
|
+
console.error(`\u274C Elevate fatal error: ${detail}`);
|
|
6518
3057
|
process.exit(1);
|
|
6519
3058
|
});
|
|
6520
3059
|
//# sourceMappingURL=cli.js.map
|