@dennisrepa/elevate 1.7.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 +8 -5
- package/dist/chunk-4YCMSRA6.js +5817 -0
- package/dist/chunk-4YCMSRA6.js.map +1 -0
- package/dist/cli.js +136 -3604
- 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 +104 -24
- package/docs/en.md +104 -24
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -1,3505 +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.7.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 yankedLock(dep, literal, origin, versions, locks);
|
|
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 yankedLock(dep, requirement, origin, versions, locks);
|
|
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 yankedLock(dep, requirement, origin, versions, locks) {
|
|
1788
|
-
const locked = (locks.get(dep.name) ?? []).filter((v) => satisfiesRequirement(requirement, v)).sort(semver8.rcompare)[0];
|
|
1789
|
-
if (!locked || !versions.some((v) => v.yanked && v.version === locked)) return null;
|
|
1790
|
-
return { skipped: { identifier: dep.name, origin: origin.kind, reason: "locked-version-yanked", detail: locked } };
|
|
1791
|
-
}
|
|
1792
|
-
function alignment(dep, target, located, common) {
|
|
1793
|
-
const local = target.version;
|
|
1794
|
-
if (!located || !local || !semver8.valid(local)) return null;
|
|
1795
|
-
const literal = located.version.value;
|
|
1796
|
-
if (!isUpgradableRequirement(literal) || satisfiesRequirement(literal, local)) return null;
|
|
1797
|
-
const floor = requirementFloor(literal) ?? "0.0.0";
|
|
1798
|
-
const diff = cargoDiff(floor, local);
|
|
1799
|
-
const tag = semver8.prerelease(local) ? extractPreReleaseTag(local) ?? "PRE" : void 0;
|
|
1800
|
-
return {
|
|
1801
|
-
candidate: {
|
|
1802
|
-
coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "cargo" },
|
|
1803
|
-
currentRange: literal,
|
|
1804
|
-
currentClean: floor,
|
|
1805
|
-
latest: local,
|
|
1806
|
-
newRange: rewriteRequirement(literal, local),
|
|
1807
|
-
diff,
|
|
1808
|
-
scope: common.scope,
|
|
1809
|
-
selected: diff !== "major",
|
|
1810
|
-
origin: { kind: "workspace", moduleId: target.id, moduleRelPath: target.relPath },
|
|
1811
|
-
action: "align",
|
|
1812
|
-
declaration: located.declaration,
|
|
1813
|
-
availableVersions: [local],
|
|
1814
|
-
isPreRelease: tag !== void 0,
|
|
1815
|
-
preReleaseTag: tag,
|
|
1816
|
-
...common.qualifier ? { qualifier: common.qualifier } : {}
|
|
1817
|
-
}
|
|
1818
|
-
};
|
|
1819
|
-
}
|
|
1820
|
-
function groupDependencies(deps) {
|
|
1821
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1822
|
-
for (const dep of deps) {
|
|
1823
|
-
const id = `${dep.rename ?? dep.name}`;
|
|
1824
|
-
groups.set(id, [...groups.get(id) ?? [], dep]);
|
|
1825
|
-
}
|
|
1826
|
-
return [...groups.values()];
|
|
1827
|
-
}
|
|
1828
|
-
function readDeclarations(manifestPath) {
|
|
1829
|
-
try {
|
|
1830
|
-
return parseCargoManifest(readFileSync11(manifestPath, "utf8"));
|
|
1831
|
-
} catch {
|
|
1832
|
-
return [];
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
var sectionOf = (dep) => dep.kind === "dev" ? "dev-dependencies" : dep.kind === "build" ? "build-dependencies" : "dependencies";
|
|
1836
|
-
function findDeclaration(decls, key, dep) {
|
|
1837
|
-
const candidates = decls.filter((d) => !d.inWorkspace && d.key === key);
|
|
1838
|
-
return candidates.find((d) => d.section === sectionOf(dep) && (d.target ?? null) === dep.target) ?? candidates[0];
|
|
1839
|
-
}
|
|
1840
|
-
function locate(decl, root, key, repoRoot, manifestPath, rootManifest) {
|
|
1841
|
-
if (!decl) return void 0;
|
|
1842
|
-
if (decl.inherited) {
|
|
1843
|
-
const shared = root.find((d) => d.inWorkspace && d.key === key);
|
|
1844
|
-
if (!shared?.version) return void 0;
|
|
1845
|
-
return {
|
|
1846
|
-
version: shared.version,
|
|
1847
|
-
inherited: true,
|
|
1848
|
-
declaration: { file: rootManifest, kind: "dependency-management", displayPath: display(repoRoot, rootManifest) }
|
|
1849
|
-
};
|
|
1850
|
-
}
|
|
1851
|
-
if (!decl.version) return void 0;
|
|
1852
|
-
return {
|
|
1853
|
-
version: decl.version,
|
|
1854
|
-
inherited: false,
|
|
1855
|
-
declaration: { file: manifestPath, kind: "dependency", displayPath: display(repoRoot, manifestPath) }
|
|
1856
|
-
};
|
|
1857
|
-
}
|
|
1858
|
-
function sharersOf(metadata, self, key) {
|
|
1859
|
-
return metadata.packages.filter((p) => p !== self).filter((p) => readDeclarations(p.manifest_path).some((d) => d.key === key && d.inherited)).map((p) => p.name);
|
|
1860
|
-
}
|
|
1861
|
-
var display = (repoRoot, file) => relative4(repoRoot, file).replace(/\\/g, "/");
|
|
1862
|
-
var samePath = (a, b) => relative4(a, b) === "";
|
|
1863
|
-
function sameRequirement(literal, req) {
|
|
1864
|
-
const a = toSemverRange(literal);
|
|
1865
|
-
const b = toSemverRange(req);
|
|
1866
|
-
return a !== null && b !== null && semver8.validRange(a) === semver8.validRange(b);
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
// src/adapters/cargo/cargo-updater.ts
|
|
1870
|
-
import { existsSync as existsSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync2 } from "fs";
|
|
1871
|
-
import { join as join12, resolve as resolve6 } from "path";
|
|
1872
|
-
import semver9 from "semver";
|
|
1873
|
-
|
|
1874
|
-
// src/adapters/shared/xml.ts
|
|
1875
|
-
import { SaxesParser } from "saxes";
|
|
1876
|
-
var XmlParseError = class extends Error {
|
|
1877
|
-
constructor(message2, file) {
|
|
1878
|
-
super(file ? `${file}: ${message2}` : message2);
|
|
1879
|
-
this.file = file;
|
|
1880
|
-
this.name = "XmlParseError";
|
|
1881
|
-
}
|
|
1882
|
-
file;
|
|
1883
|
-
};
|
|
1884
|
-
function parseXml(source, file) {
|
|
1885
|
-
const parser = new SaxesParser({ position: true });
|
|
1886
|
-
const stack = [];
|
|
1887
|
-
const openEnds = [];
|
|
1888
|
-
let root;
|
|
1889
|
-
let failure;
|
|
1890
|
-
parser.on("error", (err) => {
|
|
1891
|
-
failure ??= err;
|
|
1892
|
-
});
|
|
1893
|
-
parser.on("opentag", (tag) => {
|
|
1894
|
-
const attributes = {};
|
|
1895
|
-
for (const [key, value] of Object.entries(tag.attributes)) {
|
|
1896
|
-
attributes[key] = typeof value === "string" ? value : value.value;
|
|
1897
|
-
}
|
|
1898
|
-
const element = {
|
|
1899
|
-
name: localName(tag.name),
|
|
1900
|
-
attributes,
|
|
1901
|
-
children: [],
|
|
1902
|
-
text: ""
|
|
1903
|
-
};
|
|
1904
|
-
const parent = stack[stack.length - 1];
|
|
1905
|
-
if (parent) parent.children.push(element);
|
|
1906
|
-
else root ??= element;
|
|
1907
|
-
stack.push(element);
|
|
1908
|
-
openEnds.push(parser.position);
|
|
1909
|
-
});
|
|
1910
|
-
parser.on("closetag", (tag) => {
|
|
1911
|
-
const element = stack.pop();
|
|
1912
|
-
const openEnd = openEnds.pop();
|
|
1913
|
-
if (!element || openEnd === void 0 || tag.isSelfClosing) return;
|
|
1914
|
-
const closeEnd = parser.position;
|
|
1915
|
-
const closeStart = source.lastIndexOf("</", closeEnd - 1);
|
|
1916
|
-
if (closeStart >= openEnd) {
|
|
1917
|
-
element.contentRange = { start: openEnd, end: closeStart };
|
|
1918
|
-
}
|
|
1919
|
-
});
|
|
1920
|
-
parser.on("text", (text) => {
|
|
1921
|
-
const current = stack[stack.length - 1];
|
|
1922
|
-
if (current) current.text += text;
|
|
1923
|
-
});
|
|
1924
|
-
parser.on("cdata", (data) => {
|
|
1925
|
-
const current = stack[stack.length - 1];
|
|
1926
|
-
if (current) current.text += data;
|
|
1927
|
-
});
|
|
1928
|
-
try {
|
|
1929
|
-
parser.write(source).close();
|
|
1930
|
-
} catch (err) {
|
|
1931
|
-
failure ??= err;
|
|
1932
|
-
}
|
|
1933
|
-
if (failure) throw new XmlParseError(failure.message, file);
|
|
1934
|
-
if (!root) throw new XmlParseError("document has no root element", file);
|
|
1935
|
-
return root;
|
|
1936
|
-
}
|
|
1937
|
-
function child(element, name) {
|
|
1938
|
-
return element?.children.find((c) => c.name === name);
|
|
1939
|
-
}
|
|
1940
|
-
function children(element, name) {
|
|
1941
|
-
return element ? element.children.filter((c) => c.name === name) : [];
|
|
1942
|
-
}
|
|
1943
|
-
function childAt(element, ...path) {
|
|
1944
|
-
let current = element;
|
|
1945
|
-
for (const name of path) current = child(current, name);
|
|
1946
|
-
return current;
|
|
1947
|
-
}
|
|
1948
|
-
function childText(element, name) {
|
|
1949
|
-
const value = child(element, name)?.text.trim();
|
|
1950
|
-
return value ? value : void 0;
|
|
1951
|
-
}
|
|
1952
|
-
var StaleEditError = class extends Error {
|
|
1953
|
-
constructor(message2) {
|
|
1954
|
-
super(message2);
|
|
1955
|
-
this.name = "StaleEditError";
|
|
1956
|
-
}
|
|
1957
|
-
};
|
|
1958
|
-
function applyTextEdits(source, edits) {
|
|
1959
|
-
const splices = edits.map((edit) => {
|
|
1960
|
-
const range = edit.element.contentRange;
|
|
1961
|
-
if (!range) {
|
|
1962
|
-
throw new StaleEditError(`<${edit.element.name}> has no editable text content.`);
|
|
1963
|
-
}
|
|
1964
|
-
const raw = source.slice(range.start, range.end);
|
|
1965
|
-
const leading = raw.length - raw.trimStart().length;
|
|
1966
|
-
const value = raw.trim();
|
|
1967
|
-
if (value !== edit.expected) {
|
|
1968
|
-
throw new StaleEditError(
|
|
1969
|
-
`<${edit.element.name}> contains '${value}', expected '${edit.expected}'. The file changed since it was scanned.`
|
|
1970
|
-
);
|
|
1971
|
-
}
|
|
1972
|
-
const start = range.start + leading;
|
|
1973
|
-
return { start, end: start + value.length, replacement: edit.replacement };
|
|
1974
|
-
});
|
|
1975
|
-
splices.sort((a, b) => b.start - a.start);
|
|
1976
|
-
for (let i = 1; i < splices.length; i++) {
|
|
1977
|
-
if (splices[i].end > splices[i - 1].start) {
|
|
1978
|
-
throw new StaleEditError("Overlapping edits on the same element.");
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
let result = source;
|
|
1982
|
-
for (const splice of splices) {
|
|
1983
|
-
result = result.slice(0, splice.start) + splice.replacement + result.slice(splice.end);
|
|
1984
|
-
}
|
|
1985
|
-
return result;
|
|
1986
|
-
}
|
|
1987
|
-
function escapeXml(value) {
|
|
1988
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1989
|
-
}
|
|
1990
|
-
function serializeXml(element, indent = "") {
|
|
1991
|
-
const attrs = Object.entries(element.attributes).map(([key, value]) => ` ${key}="${escapeXml(value).replace(/"/g, """)}"`).join("");
|
|
1992
|
-
if (element.children.length === 0) {
|
|
1993
|
-
return `${indent}<${element.name}${attrs}>${escapeXml(element.text.trim())}</${element.name}>`;
|
|
1994
|
-
}
|
|
1995
|
-
const inner = element.children.map((c) => serializeXml(c, `${indent} `)).join("\n");
|
|
1996
|
-
return `${indent}<${element.name}${attrs}>
|
|
1997
|
-
${inner}
|
|
1998
|
-
${indent}</${element.name}>`;
|
|
1999
|
-
}
|
|
2000
|
-
function localName(qualified) {
|
|
2001
|
-
const colon = qualified.indexOf(":");
|
|
2002
|
-
return colon >= 0 ? qualified.slice(colon + 1) : qualified;
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
// src/adapters/shared/text-edit.ts
|
|
2006
|
-
function applyRangeEdits(source, edits) {
|
|
2007
|
-
for (const edit of edits) {
|
|
2008
|
-
const found = source.slice(edit.start, edit.end);
|
|
2009
|
-
if (found !== edit.expected) {
|
|
2010
|
-
throw new StaleEditError(`Found '${found}', expected '${edit.expected}'. The file changed since it was scanned.`);
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
const ordered = [...edits].sort((a, b) => b.start - a.start);
|
|
2014
|
-
for (let i = 1; i < ordered.length; i++) {
|
|
2015
|
-
if (ordered[i].end > ordered[i - 1].start) throw new StaleEditError("Overlapping edits on the same text.");
|
|
2016
|
-
}
|
|
2017
|
-
let result = source;
|
|
2018
|
-
for (const edit of ordered) result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);
|
|
2019
|
-
return result;
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
// src/adapters/cargo/cargo-updater.ts
|
|
2023
|
-
var UPDATE_TIMEOUT_MS = 10 * 6e4;
|
|
2024
|
-
var workspaceDir = (module, rootDir) => module.aggregatorDir ?? module.path ?? rootDir;
|
|
2025
|
-
var CargoUpdaterAdapter = class {
|
|
2026
|
-
affectedFiles(module, rootDir, updates) {
|
|
2027
|
-
const files = new Set(updates.map((u) => resolve6(u.declaration?.file ?? join12(module.path, "Cargo.toml"))));
|
|
2028
|
-
files.add(join12(workspaceDir(module, rootDir), "Cargo.lock"));
|
|
2029
|
-
return [...files];
|
|
2030
|
-
}
|
|
2031
|
-
async applyUpdates(module, rootDir, updates, onProgress) {
|
|
2032
|
-
const dir = workspaceDir(module, rootDir);
|
|
2033
|
-
const lockFile = join12(dir, "Cargo.lock");
|
|
2034
|
-
const lockedBefore = readLockedVersions(lockFile);
|
|
2035
|
-
const lockUpdates = updates.filter((u) => u.action === "lock");
|
|
2036
|
-
updates = updates.filter((u) => u.action !== "lock");
|
|
2037
|
-
this.checkLockUpdates(lockUpdates, lockFile, lockedBefore);
|
|
2038
|
-
const byFile = /* @__PURE__ */ new Map();
|
|
2039
|
-
for (const update of updates) {
|
|
2040
|
-
const file = resolve6(update.declaration?.file ?? join12(module.path, "Cargo.toml"));
|
|
2041
|
-
byFile.set(file, [...byFile.get(file) ?? [], update]);
|
|
2042
|
-
}
|
|
2043
|
-
const writes = [];
|
|
2044
|
-
for (const [file, fileUpdates] of byFile) {
|
|
2045
|
-
onProgress(`Preparing ${fileUpdates.length} change(s) in ${file}\u2026`);
|
|
2046
|
-
const source = readFileSync12(file, "utf8");
|
|
2047
|
-
const decls = parseCargoManifest(source);
|
|
2048
|
-
const edits = [];
|
|
2049
|
-
for (const update of fileUpdates) {
|
|
2050
|
-
const inWorkspace = update.declaration?.kind === "dependency-management";
|
|
2051
|
-
const name = update.coordinate.identifier;
|
|
2052
|
-
const found = decls.filter(
|
|
2053
|
-
(d) => d.inWorkspace === inWorkspace && (d.package ?? d.key) === name && d.version?.value === update.currentRange
|
|
2054
|
-
);
|
|
2055
|
-
if (found.length === 0) {
|
|
2056
|
-
throw new InstallError(`${file}: cannot find the version of ${name} (${update.currentRange}) any more; rescan the module.`);
|
|
2057
|
-
}
|
|
2058
|
-
for (const d of found) {
|
|
2059
|
-
edits.push({ start: d.version.start, end: d.version.end, expected: d.version.value, replacement: update.newRange });
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
try {
|
|
2063
|
-
writes.push({ file, content: applyRangeEdits(source, edits) });
|
|
2064
|
-
} catch (err) {
|
|
2065
|
-
if (err instanceof StaleEditError) throw new InstallError(`${file}: ${err.message}`);
|
|
2066
|
-
throw err;
|
|
2067
|
-
}
|
|
2068
|
-
}
|
|
2069
|
-
onProgress("Writing Cargo.toml files\u2026");
|
|
2070
|
-
for (const write of writes) writeFileSync2(write.file, write.content, "utf8");
|
|
2071
|
-
const registryUpdates = updates.filter((u) => u.action === "update");
|
|
2072
|
-
if (existsSync8(lockFile) && registryUpdates.length > 0) {
|
|
2073
|
-
await this.refreshLock(dir, lockFile, lockedBefore, registryUpdates, onProgress);
|
|
2074
|
-
}
|
|
2075
|
-
for (const update of lockUpdates) {
|
|
2076
|
-
const name = update.coordinate.identifier;
|
|
2077
|
-
onProgress(`Locking ${name} at ${update.latest}\u2026`);
|
|
2078
|
-
await this.cargoUpdate(dir, ["-p", `${name}@${update.currentClean}`, "--precise", update.latest]);
|
|
2079
|
-
}
|
|
2080
|
-
const parts = [];
|
|
2081
|
-
if (updates.length > 0) {
|
|
2082
|
-
parts.push(`Updated ${updates.length} requirement(s) in ${writes.length} Cargo.toml file(s)${existsSync8(lockFile) ? "; Cargo.lock refreshed" : ""}`);
|
|
2083
|
-
}
|
|
2084
|
-
if (lockUpdates.length > 0) parts.push(`locked ${lockUpdates.length} crate(s) at a newer version in Cargo.lock`);
|
|
2085
|
-
const message2 = parts.join("; ");
|
|
2086
|
-
return {
|
|
2087
|
-
updatedCount: updates.length + lockUpdates.length,
|
|
2088
|
-
auditMessage: `${message2.charAt(0).toUpperCase()}${message2.slice(1)}.`,
|
|
2089
|
-
auditSeverity: "clean",
|
|
2090
|
-
integrityViolations: []
|
|
2091
|
-
};
|
|
2092
|
-
}
|
|
2093
|
-
/** A lock-only update needs a lock file that still holds the version the scan saw. */
|
|
2094
|
-
checkLockUpdates(lockUpdates, lockFile, locked) {
|
|
2095
|
-
if (lockUpdates.length === 0) return;
|
|
2096
|
-
if (!existsSync8(lockFile)) {
|
|
2097
|
-
throw new InstallError(`${lockFile} does not exist any more; rescan the module.`);
|
|
2098
|
-
}
|
|
2099
|
-
for (const update of lockUpdates) {
|
|
2100
|
-
const name = update.coordinate.identifier;
|
|
2101
|
-
if (!(locked.get(name) ?? []).includes(update.currentClean)) {
|
|
2102
|
-
throw new InstallError(`Cargo.lock no longer locks ${name} at ${update.currentClean}; rescan the module.`);
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
async resync() {
|
|
2107
|
-
}
|
|
2108
|
-
async refreshLock(dir, lockFile, before, updates, onProgress) {
|
|
2109
|
-
const specs = [];
|
|
2110
|
-
for (const update of updates) {
|
|
2111
|
-
const name = update.coordinate.identifier;
|
|
2112
|
-
const versions = before.get(name) ?? [];
|
|
2113
|
-
const old = versions.find((v) => satisfiesRequirement(update.currentRange, v)) ?? versions[0];
|
|
2114
|
-
if (old) specs.push(`${name}@${old}`);
|
|
2115
|
-
}
|
|
2116
|
-
if (specs.length === 0) return;
|
|
2117
|
-
onProgress("Updating Cargo.lock\u2026");
|
|
2118
|
-
await this.cargoUpdate(dir, specs.flatMap((s) => ["-p", s]));
|
|
2119
|
-
const after = readLockedVersions(lockFile);
|
|
2120
|
-
for (const update of updates) {
|
|
2121
|
-
const name = update.coordinate.identifier;
|
|
2122
|
-
const locked = (after.get(name) ?? []).find((v) => satisfiesRequirement(update.newRange, v));
|
|
2123
|
-
if (locked && semver9.valid(update.latest) && locked !== update.latest) {
|
|
2124
|
-
onProgress(`Pinning ${name} to ${update.latest}\u2026`);
|
|
2125
|
-
await this.cargoUpdate(dir, ["-p", `${name}@${locked}`, "--precise", update.latest]);
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
async cargoUpdate(dir, args) {
|
|
2130
|
-
const result = await runCommand("cargo", ["update", ...args], { cwd: dir, timeoutMs: UPDATE_TIMEOUT_MS });
|
|
2131
|
-
if (result.exitCode !== 0) {
|
|
2132
|
-
const reason = result.timedOut ? "cargo update timed out" : "cargo update failed";
|
|
2133
|
-
throw new InstallError(reason, tail(result.stderr || result.stdout));
|
|
2134
|
-
}
|
|
2135
|
-
}
|
|
2136
|
-
};
|
|
2137
|
-
|
|
2138
|
-
// src/adapters/cargo/cargo-verifier.ts
|
|
2139
|
-
import { existsSync as existsSync9 } from "fs";
|
|
2140
|
-
import { join as join13, relative as relative5, resolve as resolve7 } from "path";
|
|
2141
|
-
var VERIFY_TIMEOUT_MS2 = 30 * 6e4;
|
|
2142
|
-
var same = (a, b) => relative5(resolve7(a), resolve7(b)) === "";
|
|
2143
|
-
function planCheck(metadata, module, changedFiles, locked) {
|
|
2144
|
-
const args = ["check", "--all-targets", ...locked ? ["--locked"] : []];
|
|
2145
|
-
const rootManifest = join13(metadata.workspace_root, "Cargo.toml");
|
|
2146
|
-
const lockFile = join13(metadata.workspace_root, "Cargo.lock");
|
|
2147
|
-
const memberManifests = new Set(metadata.packages.map((p) => resolve7(p.manifest_path)));
|
|
2148
|
-
if (changedFiles.some((f) => same(f, rootManifest))) {
|
|
2149
|
-
const isVirtualRoot = !memberManifests.has(resolve7(rootManifest));
|
|
2150
|
-
if (isVirtualRoot || metadata.packages.length > 1) return [...args, "--workspace"];
|
|
2151
|
-
}
|
|
2152
|
-
if (changedFiles.length > 0 && changedFiles.every((f) => same(f, lockFile))) return [...args, "--workspace"];
|
|
2153
|
-
const changed = new Set(
|
|
2154
|
-
metadata.packages.filter((p) => changedFiles.some((f) => same(f, p.manifest_path))).map((p) => p.name)
|
|
2155
|
-
);
|
|
2156
|
-
if (changed.size === 0) changed.add(module.id);
|
|
2157
|
-
for (let grew = true; grew; ) {
|
|
2158
|
-
grew = false;
|
|
2159
|
-
for (const p of metadata.packages) {
|
|
2160
|
-
if (changed.has(p.name)) continue;
|
|
2161
|
-
if (p.dependencies.some((d) => d.source === null && d.path && changed.has(d.name))) {
|
|
2162
|
-
changed.add(p.name);
|
|
2163
|
-
grew = true;
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
const ordered = metadata.packages.filter((p) => changed.has(p.name)).map((p) => p.name);
|
|
2168
|
-
return [...args, ...ordered.flatMap((name) => ["-p", name])];
|
|
2169
|
-
}
|
|
2170
|
-
var CargoVerificationAdapter = class {
|
|
2171
|
-
constructor(deps = { loadMetadata, hasLock: (dir) => existsSync9(join13(dir, "Cargo.lock")) }) {
|
|
2172
|
-
this.deps = deps;
|
|
2173
|
-
}
|
|
2174
|
-
deps;
|
|
2175
|
-
async verify(module, rootDir, options = {}) {
|
|
2176
|
-
const dir = module.aggregatorDir ?? module.path ?? rootDir;
|
|
2177
|
-
if (options.customScript) {
|
|
2178
|
-
const label2 = options.customLabel || options.customScript;
|
|
2179
|
-
const result2 = await runShellScript(options.customScript, { cwd: dir, timeoutMs: VERIFY_TIMEOUT_MS2 });
|
|
2180
|
-
return result2.exitCode === 0 ? { status: "clean", details: "Verification script succeeded.", label: label2 } : { status: "warn", details: tail(result2.stderr || result2.stdout), label: label2 };
|
|
2181
|
-
}
|
|
2182
|
-
const metadata = await this.deps.loadMetadata(dir);
|
|
2183
|
-
const args = planCheck(metadata, module, options.changedFiles ?? [], this.deps.hasLock(dir));
|
|
2184
|
-
const label = `Cargo build (cargo ${args.join(" ")})`;
|
|
2185
|
-
const result = await runCargo(args, { cwd: dir, timeoutMs: VERIFY_TIMEOUT_MS2 });
|
|
2186
|
-
if (result.exitCode === 0) return { status: "clean", details: "All affected crates type-check, including tests.", label };
|
|
2187
|
-
return {
|
|
2188
|
-
status: "warn",
|
|
2189
|
-
details: result.timedOut ? "cargo check timed out." : cargoErrors(`${result.stderr}
|
|
2190
|
-
${result.stdout}`),
|
|
2191
|
-
label
|
|
2192
|
-
};
|
|
2193
|
-
}
|
|
2194
|
-
};
|
|
2195
|
-
|
|
2196
|
-
// src/adapters/cargo/cargo-strategy.ts
|
|
2197
|
-
var cargoVersionPolicy = {
|
|
2198
|
-
normalize: (v) => semver10.valid(v),
|
|
2199
|
-
rewrite: rewriteRequirement,
|
|
2200
|
-
diff: (current, target) => semver10.valid(current) && semver10.valid(target) ? semver10.eq(current, target) ? "patch" : cargoDiff(current, target) : "minor",
|
|
2201
|
-
// Updates are written where the scan found the declaration.
|
|
2202
|
-
allowsManualTargets: false
|
|
2203
|
-
};
|
|
2204
|
-
var CargoEcosystemStrategy = class {
|
|
2205
|
-
ecosystem = "cargo";
|
|
2206
|
-
displayName = "Rust / Cargo";
|
|
2207
|
-
icon = "\u{1F980}";
|
|
2208
|
-
manifestFile = "Cargo.toml";
|
|
2209
|
-
versioning = cargoVersionPolicy;
|
|
2210
|
-
config = new CargoConfigReader();
|
|
2211
|
-
index = new CargoIndexClient();
|
|
2212
|
-
endpoint = (registry, cwd) => this.config.resolve(registry, cwd);
|
|
2213
|
-
discovery = new CargoModuleDiscoveryAdapter();
|
|
2214
|
-
registry = new CargoRegistryAdapter({ index: this.index, endpoint: this.endpoint });
|
|
2215
|
-
reader = new CargoDependencyAdapter({ loadMetadata, index: this.index, endpoint: this.endpoint });
|
|
2216
|
-
updater = new CargoUpdaterAdapter();
|
|
2217
|
-
verifier = new CargoVerificationAdapter();
|
|
2218
|
-
getTabLabels(counts, t) {
|
|
2219
|
-
return {
|
|
2220
|
-
all: t.tabs.all(counts.total),
|
|
2221
|
-
prod: t.tabs.dependencies(counts.prodCount),
|
|
2222
|
-
dev: t.tabs.devDependencies(counts.devCount)
|
|
2223
|
-
};
|
|
2224
|
-
}
|
|
2225
|
-
};
|
|
2226
|
-
|
|
2227
|
-
// src/adapters/maven/maven-discovery.ts
|
|
2228
|
-
import { existsSync as existsSync12 } from "fs";
|
|
2229
|
-
import { dirname as dirname7, join as join16, relative as relative6, resolve as resolve10 } from "path";
|
|
2230
|
-
|
|
2231
|
-
// src/adapters/maven/maven-project.ts
|
|
2232
|
-
import { existsSync as existsSync11, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
2233
|
-
import { join as join15, resolve as resolve9 } from "path";
|
|
2234
|
-
|
|
2235
|
-
// src/adapters/maven/maven-pom.ts
|
|
2236
|
-
import { existsSync as existsSync10, readFileSync as readFileSync13, statSync } from "fs";
|
|
2237
|
-
import { dirname as dirname6, join as join14, resolve as resolve8 } from "path";
|
|
2238
|
-
function readPom(file) {
|
|
2239
|
-
const source = readFileSync13(file, "utf8");
|
|
2240
|
-
return parsePom(source, file);
|
|
2241
|
-
}
|
|
2242
|
-
function parsePom(source, file) {
|
|
2243
|
-
const project = parseXml(source, file);
|
|
2244
|
-
const parentElement = child(project, "parent");
|
|
2245
|
-
const parent = parentElement ? {
|
|
2246
|
-
groupId: childText(parentElement, "groupId") ?? "",
|
|
2247
|
-
artifactId: childText(parentElement, "artifactId") ?? "",
|
|
2248
|
-
version: childText(parentElement, "version") ?? "",
|
|
2249
|
-
relativePath: child(parentElement, "relativePath")?.text.trim(),
|
|
2250
|
-
versionElement: child(parentElement, "version")
|
|
2251
|
-
} : void 0;
|
|
2252
|
-
const properties = /* @__PURE__ */ new Map();
|
|
2253
|
-
for (const element of child(project, "properties")?.children ?? []) {
|
|
2254
|
-
properties.set(element.name, { value: element.text.trim(), element });
|
|
2255
|
-
}
|
|
2256
|
-
return {
|
|
2257
|
-
file,
|
|
2258
|
-
dir: dirname6(file),
|
|
2259
|
-
groupId: childText(project, "groupId"),
|
|
2260
|
-
artifactId: childText(project, "artifactId") ?? "unknown",
|
|
2261
|
-
version: childText(project, "version"),
|
|
2262
|
-
versionElement: child(project, "version"),
|
|
2263
|
-
name: childText(project, "name"),
|
|
2264
|
-
packaging: childText(project, "packaging") ?? "jar",
|
|
2265
|
-
parent,
|
|
2266
|
-
properties,
|
|
2267
|
-
dependencies: readDependencies(child(project, "dependencies")),
|
|
2268
|
-
managedDependencies: readDependencies(childAt(project, "dependencyManagement", "dependencies")),
|
|
2269
|
-
modules: children(child(project, "modules"), "module").map((m) => m.text.trim()).filter(Boolean)
|
|
2270
|
-
};
|
|
2271
|
-
}
|
|
2272
|
-
function readDependencies(container) {
|
|
2273
|
-
return children(container, "dependency").map((element) => ({
|
|
2274
|
-
groupId: childText(element, "groupId") ?? "",
|
|
2275
|
-
artifactId: childText(element, "artifactId") ?? "",
|
|
2276
|
-
version: childText(element, "version"),
|
|
2277
|
-
type: childText(element, "type"),
|
|
2278
|
-
classifier: childText(element, "classifier"),
|
|
2279
|
-
scope: childText(element, "scope"),
|
|
2280
|
-
versionElement: child(element, "version")
|
|
2281
|
-
}));
|
|
2282
|
-
}
|
|
2283
|
-
function parentPomFile(pom) {
|
|
2284
|
-
if (!pom.parent) return void 0;
|
|
2285
|
-
const relativePath = pom.parent.relativePath ?? "../pom.xml";
|
|
2286
|
-
if (relativePath === "") return void 0;
|
|
2287
|
-
let candidate = resolve8(pom.dir, relativePath);
|
|
2288
|
-
if (existsSync10(candidate) && statSync(candidate).isDirectory()) candidate = join14(candidate, "pom.xml");
|
|
2289
|
-
return existsSync10(candidate) ? candidate : void 0;
|
|
2290
|
-
}
|
|
2291
|
-
function builtinProperty(name, module) {
|
|
2292
|
-
switch (name) {
|
|
2293
|
-
case "project.groupId":
|
|
2294
|
-
case "pom.groupId":
|
|
2295
|
-
return module.groupId ?? module.parent?.groupId;
|
|
2296
|
-
case "project.artifactId":
|
|
2297
|
-
case "pom.artifactId":
|
|
2298
|
-
return module.artifactId;
|
|
2299
|
-
case "project.version":
|
|
2300
|
-
case "pom.version":
|
|
2301
|
-
return module.version ?? module.parent?.version;
|
|
2302
|
-
case "project.parent.groupId":
|
|
2303
|
-
return module.parent?.groupId;
|
|
2304
|
-
case "project.parent.version":
|
|
2305
|
-
return module.parent?.version;
|
|
2306
|
-
default:
|
|
2307
|
-
return void 0;
|
|
2308
|
-
}
|
|
2309
|
-
}
|
|
2310
|
-
function findProperty(name, chain) {
|
|
2311
|
-
for (const pom of chain) {
|
|
2312
|
-
const property = pom.properties.get(name);
|
|
2313
|
-
if (property) return { pom, property };
|
|
2314
|
-
}
|
|
2315
|
-
return void 0;
|
|
2316
|
-
}
|
|
2317
|
-
function interpolate(text, chain, depth = 0) {
|
|
2318
|
-
if (depth > 10 || !text.includes("${")) return text;
|
|
2319
|
-
const module = chain[0];
|
|
2320
|
-
const replaced = text.replace(/\$\{([^}]+)\}/g, (expression, name) => {
|
|
2321
|
-
const builtin = module ? builtinProperty(name, module) : void 0;
|
|
2322
|
-
if (builtin !== void 0) return builtin;
|
|
2323
|
-
return findProperty(name, chain)?.property.value ?? expression;
|
|
2324
|
-
});
|
|
2325
|
-
return replaced === text ? text : interpolate(replaced, chain, depth + 1);
|
|
2326
|
-
}
|
|
2327
|
-
function propertyReference(text) {
|
|
2328
|
-
return text.match(/^\$\{([^}]+)\}$/)?.[1];
|
|
2329
|
-
}
|
|
2330
|
-
|
|
2331
|
-
// src/adapters/maven/maven-project.ts
|
|
2332
|
-
var MAX_DEPTH = 6;
|
|
2333
|
-
var SKIPPED_DIRS = /* @__PURE__ */ new Set(["node_modules", "target", "build", "dist", "out"]);
|
|
2334
|
-
var MavenProject = class _MavenProject {
|
|
2335
|
-
constructor(rootDir, poms) {
|
|
2336
|
-
this.rootDir = rootDir;
|
|
2337
|
-
this.poms = poms;
|
|
2338
|
-
for (const pom of poms) this.byFile.set(pom.file, pom);
|
|
2339
|
-
this.assignAggregators();
|
|
2340
|
-
}
|
|
2341
|
-
rootDir;
|
|
2342
|
-
poms;
|
|
2343
|
-
byFile = /* @__PURE__ */ new Map();
|
|
2344
|
-
aggregators = /* @__PURE__ */ new Map();
|
|
2345
|
-
/**
|
|
2346
|
-
* Loads every readable pom.xml below `rootDir`, plus every module listed in
|
|
2347
|
-
* a `<modules>` section, even in directories the file walk skips (such as
|
|
2348
|
-
* `build/` or `dist/`). Malformed files are skipped.
|
|
2349
|
-
*/
|
|
2350
|
-
static load(rootDir) {
|
|
2351
|
-
const poms = [];
|
|
2352
|
-
const queue = findPomFiles(rootDir);
|
|
2353
|
-
const seen = new Set(queue);
|
|
2354
|
-
while (queue.length > 0) {
|
|
2355
|
-
const file = queue.shift();
|
|
2356
|
-
let pom;
|
|
2357
|
-
try {
|
|
2358
|
-
pom = readPom(file);
|
|
2359
|
-
} catch {
|
|
2360
|
-
continue;
|
|
2361
|
-
}
|
|
2362
|
-
poms.push(pom);
|
|
2363
|
-
for (const entry of pom.modules) {
|
|
2364
|
-
const target = resolve9(pom.dir, entry);
|
|
2365
|
-
const moduleFile = target.endsWith(".xml") ? target : join15(target, "pom.xml");
|
|
2366
|
-
if (!seen.has(moduleFile) && existsSync11(moduleFile)) {
|
|
2367
|
-
seen.add(moduleFile);
|
|
2368
|
-
queue.push(moduleFile);
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
return new _MavenProject(rootDir, poms);
|
|
2373
|
-
}
|
|
2374
|
-
pomAt(file) {
|
|
2375
|
-
return this.byFile.get(resolve9(file));
|
|
2376
|
-
}
|
|
2377
|
-
/** The project followed by its local parents, stopping at the first external parent. */
|
|
2378
|
-
chainOf(pom) {
|
|
2379
|
-
const poms = [pom];
|
|
2380
|
-
let current = pom;
|
|
2381
|
-
while (current.parent) {
|
|
2382
|
-
const parent = this.localParentOf(current);
|
|
2383
|
-
if (!parent || poms.includes(parent)) {
|
|
2384
|
-
return { poms, externalParent: parent ? void 0 : current.parent };
|
|
2385
|
-
}
|
|
2386
|
-
poms.push(parent);
|
|
2387
|
-
current = parent;
|
|
2388
|
-
}
|
|
2389
|
-
return { poms };
|
|
2390
|
-
}
|
|
2391
|
-
/** Coordinates of a project with inherited groupId/version and interpolated values. */
|
|
2392
|
-
coordinatesOf(pom) {
|
|
2393
|
-
const chain = this.chainOf(pom).poms;
|
|
2394
|
-
const groupId = pom.groupId ?? pom.parent?.groupId ?? "";
|
|
2395
|
-
const rawVersion = pom.version ?? pom.parent?.version;
|
|
2396
|
-
const version = rawVersion ? interpolate(rawVersion, chain) : void 0;
|
|
2397
|
-
return {
|
|
2398
|
-
groupId: interpolate(groupId, chain),
|
|
2399
|
-
artifactId: pom.artifactId,
|
|
2400
|
-
version: version && !version.includes("${") ? version : void 0
|
|
2401
|
-
};
|
|
2402
|
-
}
|
|
2403
|
-
/** Directory of the outermost aggregator whose reactor includes this project. */
|
|
2404
|
-
aggregatorDirOf(pom) {
|
|
2405
|
-
return this.aggregators.get(pom.file);
|
|
2406
|
-
}
|
|
2407
|
-
localParentOf(pom) {
|
|
2408
|
-
const parent = pom.parent;
|
|
2409
|
-
if (!parent) return void 0;
|
|
2410
|
-
const matches = (candidate) => {
|
|
2411
|
-
if (!candidate) return false;
|
|
2412
|
-
const groupId = interpolate(candidate.groupId ?? candidate.parent?.groupId ?? "", [candidate]);
|
|
2413
|
-
const version = interpolate(candidate.version ?? candidate.parent?.version ?? "", [candidate]);
|
|
2414
|
-
const expected = interpolate(parent.version, [pom]);
|
|
2415
|
-
return groupId === parent.groupId && candidate.artifactId === parent.artifactId && (version.includes("${") || expected.includes("${") || version === expected);
|
|
2416
|
-
};
|
|
2417
|
-
const file = parentPomFile(pom);
|
|
2418
|
-
const byPath = file ? this.byFile.get(resolve9(file)) : void 0;
|
|
2419
|
-
return matches(byPath) ? byPath : void 0;
|
|
2420
|
-
}
|
|
2421
|
-
/**
|
|
2422
|
-
* Walks `<modules>` from every aggregator, outermost first, and records for
|
|
2423
|
-
* each reached project the aggregator that builds it.
|
|
2424
|
-
*/
|
|
2425
|
-
assignAggregators() {
|
|
2426
|
-
const aggregatorPoms = this.poms.filter((pom) => pom.modules.length > 0).sort((a, b) => a.dir.length - b.dir.length);
|
|
2427
|
-
for (const aggregator of aggregatorPoms) {
|
|
2428
|
-
if (this.aggregators.has(aggregator.file)) continue;
|
|
2429
|
-
const queue = [aggregator];
|
|
2430
|
-
while (queue.length > 0) {
|
|
2431
|
-
const pom = queue.shift();
|
|
2432
|
-
if (this.aggregators.has(pom.file)) continue;
|
|
2433
|
-
this.aggregators.set(pom.file, aggregator.dir);
|
|
2434
|
-
for (const entry of pom.modules) {
|
|
2435
|
-
const target = resolve9(pom.dir, entry);
|
|
2436
|
-
const file = target.endsWith(".xml") ? target : join15(target, "pom.xml");
|
|
2437
|
-
const modulePom = this.byFile.get(file);
|
|
2438
|
-
if (modulePom) queue.push(modulePom);
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
}
|
|
2442
|
-
}
|
|
2443
|
-
};
|
|
2444
|
-
function findPomFiles(rootDir) {
|
|
2445
|
-
const results = [];
|
|
2446
|
-
const rootPom = join15(rootDir, "pom.xml");
|
|
2447
|
-
if (existsSync11(rootPom)) results.push(resolve9(rootPom));
|
|
2448
|
-
const walk = (dir, depth) => {
|
|
2449
|
-
if (depth > MAX_DEPTH) return;
|
|
2450
|
-
let entries2;
|
|
2451
|
-
try {
|
|
2452
|
-
entries2 = readdirSync2(dir, { withFileTypes: true });
|
|
2453
|
-
} catch {
|
|
2454
|
-
return;
|
|
2455
|
-
}
|
|
2456
|
-
const isProject = existsSync11(join15(dir, "pom.xml"));
|
|
2457
|
-
for (const entry of entries2) {
|
|
2458
|
-
if (!entry.isDirectory() || entry.name.startsWith(".") || SKIPPED_DIRS.has(entry.name)) continue;
|
|
2459
|
-
if (isProject && entry.name === "src") continue;
|
|
2460
|
-
const subDir = join15(dir, entry.name);
|
|
2461
|
-
const pom = join15(subDir, "pom.xml");
|
|
2462
|
-
if (existsSync11(pom) && statSync2(pom).isFile()) results.push(resolve9(pom));
|
|
2463
|
-
walk(subDir, depth + 1);
|
|
2464
|
-
}
|
|
2465
|
-
};
|
|
2466
|
-
walk(rootDir, 1);
|
|
2467
|
-
return results;
|
|
2468
|
-
}
|
|
2469
|
-
|
|
2470
|
-
// src/adapters/maven/maven-discovery.ts
|
|
2471
|
-
var MavenModuleDiscoveryAdapter = class {
|
|
2472
|
-
/**
|
|
2473
|
-
* Proposes a root only when `startDir` holds a pom.xml, and then climbs to
|
|
2474
|
-
* the POM above until there is none: the top of the connected POM chain.
|
|
2475
|
-
*/
|
|
2476
|
-
findRoot(startDir, boundaryDir) {
|
|
2477
|
-
let dir = resolve10(startDir);
|
|
2478
|
-
if (!existsSync12(join16(dir, "pom.xml"))) return void 0;
|
|
2479
|
-
const boundary = boundaryDir ? resolve10(boundaryDir) : void 0;
|
|
2480
|
-
for (; ; ) {
|
|
2481
|
-
const above = pomAbove(dir, boundary);
|
|
2482
|
-
if (!above) return dir;
|
|
2483
|
-
dir = above;
|
|
2484
|
-
}
|
|
2485
|
-
}
|
|
2486
|
-
async discover(rootDir) {
|
|
2487
|
-
const project = MavenProject.load(rootDir);
|
|
2488
|
-
const modules = project.poms.map((pom) => {
|
|
2489
|
-
const coordinates = project.coordinatesOf(pom);
|
|
2490
|
-
const relPath = relative6(rootDir, pom.dir).replace(/\\/g, "/");
|
|
2491
|
-
return {
|
|
2492
|
-
id: coordinates.groupId ? `${coordinates.groupId}:${coordinates.artifactId}` : coordinates.artifactId,
|
|
2493
|
-
name: pom.name ? interpolate(pom.name, project.chainOf(pom).poms) : pom.artifactId,
|
|
2494
|
-
path: pom.dir,
|
|
2495
|
-
relPath: relPath || "Root",
|
|
2496
|
-
ecosystem: "maven",
|
|
2497
|
-
isRoot: relPath === "",
|
|
2498
|
-
version: coordinates.version,
|
|
2499
|
-
aggregatorDir: project.aggregatorDirOf(pom)
|
|
2500
|
-
};
|
|
2501
|
-
});
|
|
2502
|
-
modules.sort((a, b) => a.isRoot ? -1 : b.isRoot ? 1 : a.relPath.localeCompare(b.relPath));
|
|
2503
|
-
return modules;
|
|
2504
|
-
}
|
|
2505
|
-
};
|
|
2506
|
-
function tryReadPom(file) {
|
|
2507
|
-
try {
|
|
2508
|
-
return readPom(file);
|
|
2509
|
-
} catch {
|
|
2510
|
-
return void 0;
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
function ancestorsWithin(dir, boundary) {
|
|
2514
|
-
const result = [];
|
|
2515
|
-
let current = dir;
|
|
2516
|
-
while (current !== boundary) {
|
|
2517
|
-
const parent = dirname7(current);
|
|
2518
|
-
if (parent === current) break;
|
|
2519
|
-
result.push(parent);
|
|
2520
|
-
current = parent;
|
|
2521
|
-
}
|
|
2522
|
-
return result;
|
|
2523
|
-
}
|
|
2524
|
-
function pomAbove(dir, boundary) {
|
|
2525
|
-
const pom = tryReadPom(join16(dir, "pom.xml"));
|
|
2526
|
-
if (!pom) return void 0;
|
|
2527
|
-
const ancestors = ancestorsWithin(dir, boundary);
|
|
2528
|
-
const found = [];
|
|
2529
|
-
const parentFile = parentPomFile(pom);
|
|
2530
|
-
if (parentFile && pom.parent && ancestors.includes(dirname7(parentFile))) {
|
|
2531
|
-
const parent = tryReadPom(parentFile);
|
|
2532
|
-
const groupId = parent?.groupId ?? parent?.parent?.groupId;
|
|
2533
|
-
if (parent && parent.artifactId === pom.parent.artifactId && groupId === pom.parent.groupId) {
|
|
2534
|
-
found.push(dirname7(parentFile));
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
const ownFile = join16(dir, "pom.xml");
|
|
2538
|
-
const aggregator = ancestors.find(
|
|
2539
|
-
(ancestor) => tryReadPom(join16(ancestor, "pom.xml"))?.modules.some((entry) => {
|
|
2540
|
-
const target = resolve10(ancestor, entry);
|
|
2541
|
-
return target === dir || target === ownFile;
|
|
2542
|
-
})
|
|
2543
|
-
);
|
|
2544
|
-
if (aggregator) found.push(aggregator);
|
|
2545
|
-
return found.sort((a, b) => a.length - b.length)[0];
|
|
2546
|
-
}
|
|
2547
|
-
|
|
2548
|
-
// src/adapters/maven/maven-registry.ts
|
|
2549
|
-
import { existsSync as existsSync15 } from "fs";
|
|
2550
|
-
import { join as join19 } from "path";
|
|
2551
|
-
|
|
2552
|
-
// src/adapters/maven/maven-command.ts
|
|
2553
|
-
import { existsSync as existsSync13 } from "fs";
|
|
2554
|
-
import { dirname as dirname8, isAbsolute as isAbsolute2, join as join17, relative as relative7, resolve as resolve11, sep } from "path";
|
|
2555
|
-
var MavenUnavailableError = class extends Error {
|
|
2556
|
-
constructor() {
|
|
2557
|
-
super("Maven is not available: no Maven Wrapper (mvnw) in the project and no `mvn` on the PATH.");
|
|
2558
|
-
this.name = "MavenUnavailableError";
|
|
2559
|
-
}
|
|
2560
|
-
};
|
|
2561
|
-
var MavenCommandError = class extends Error {
|
|
2562
|
-
constructor(message2, output) {
|
|
2563
|
-
super(message2);
|
|
2564
|
-
this.output = output;
|
|
2565
|
-
this.name = "MavenCommandError";
|
|
2566
|
-
}
|
|
2567
|
-
output;
|
|
2568
|
-
};
|
|
2569
|
-
var IS_WINDOWS2 = process.platform === "win32";
|
|
2570
|
-
var WRAPPER = IS_WINDOWS2 ? "mvnw.cmd" : "mvnw";
|
|
2571
|
-
var BATCH_ARGS = ["-B", "--no-transfer-progress"];
|
|
2572
|
-
var mvnOnPath;
|
|
2573
|
-
function findMavenWrapper(fromDir, rootDir) {
|
|
2574
|
-
let dir = resolve11(fromDir);
|
|
2575
|
-
const root = resolve11(rootDir);
|
|
2576
|
-
for (; ; ) {
|
|
2577
|
-
const candidate = join17(dir, WRAPPER);
|
|
2578
|
-
if (existsSync13(candidate)) return candidate;
|
|
2579
|
-
if (dir === root || relative7(root, dir).startsWith("..")) return void 0;
|
|
2580
|
-
const parent = dirname8(dir);
|
|
2581
|
-
if (parent === dir) return void 0;
|
|
2582
|
-
dir = parent;
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2585
|
-
function relativeToCwd(cwd, file) {
|
|
2586
|
-
const path = relative7(cwd, file);
|
|
2587
|
-
return isAbsolute2(path) ? file : path || ".";
|
|
2588
|
-
}
|
|
2589
|
-
async function runMaven(args, options) {
|
|
2590
|
-
const wrapper = findMavenWrapper(options.cwd, options.rootDir);
|
|
2591
|
-
const run = { cwd: options.cwd, timeoutMs: options.timeoutMs };
|
|
2592
|
-
if (wrapper) {
|
|
2593
|
-
const relativeWrapper = relativeToCwd(options.cwd, wrapper);
|
|
2594
|
-
const path = isAbsolute2(relativeWrapper) || relativeWrapper.includes(sep) ? relativeWrapper : `.${sep}${relativeWrapper}`;
|
|
2595
|
-
return IS_WINDOWS2 ? runCommand(path, [...BATCH_ARGS, ...args], run) : runCommand("sh", [path, ...BATCH_ARGS, ...args], run);
|
|
2596
|
-
}
|
|
2597
|
-
mvnOnPath ??= runCommand("mvn", ["-v"], { cwd: options.cwd, timeoutMs: 3e4 }).then(
|
|
2598
|
-
(result) => result.exitCode === 0,
|
|
2599
|
-
() => false
|
|
2600
|
-
);
|
|
2601
|
-
if (!await mvnOnPath) throw new MavenUnavailableError();
|
|
2602
|
-
return runCommand("mvn", [...BATCH_ARGS, ...args], run);
|
|
2603
|
-
}
|
|
2604
|
-
function mavenErrors(output) {
|
|
2605
|
-
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"));
|
|
2606
|
-
return (errors.length > 0 ? errors : output.trim().split(/\r?\n/).slice(-10)).slice(0, 12).join("\n");
|
|
2607
|
-
}
|
|
2608
|
-
|
|
2609
|
-
// src/adapters/maven/maven-resolution.ts
|
|
2610
|
-
import { randomUUID } from "crypto";
|
|
2611
|
-
import { existsSync as existsSync14, mkdirSync, readFileSync as readFileSync14, readdirSync as readdirSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2612
|
-
import { join as join18 } from "path";
|
|
2613
|
-
var DEFAULT_MAVEN_PLUGINS = { help: "3.5.2", versions: "2.22.0" };
|
|
2614
|
-
var HELP_PLUGIN_ID = "org.apache.maven.plugins:maven-help-plugin";
|
|
2615
|
-
var VERSIONS_PLUGIN_ID = "org.codehaus.mojo:versions-maven-plugin";
|
|
2616
|
-
var DOWNLOAD_FAILURES = [
|
|
2617
|
-
"could not be resolved",
|
|
2618
|
-
"could not find artifact",
|
|
2619
|
-
"failed to read artifact descriptor",
|
|
2620
|
-
"could not transfer artifact"
|
|
2621
|
-
];
|
|
2622
|
-
var MavenPluginUnavailableError = class extends MavenCommandError {
|
|
2623
|
-
constructor(plugin, output) {
|
|
2624
|
-
super(
|
|
2625
|
-
`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.
|
|
2626
|
-
${mavenErrors(output)}`,
|
|
2627
|
-
output
|
|
2628
|
-
);
|
|
2629
|
-
this.name = "MavenPluginUnavailableError";
|
|
2630
|
-
}
|
|
2631
|
-
};
|
|
2632
|
-
function isPluginUnavailable(output, pluginId) {
|
|
2633
|
-
return output.split(/\r?\n/).some((line) => {
|
|
2634
|
-
const lower = line.toLowerCase();
|
|
2635
|
-
return line.includes(pluginId) && DOWNLOAD_FAILURES.some((phrase) => lower.includes(phrase));
|
|
2636
|
-
});
|
|
2637
|
-
}
|
|
2638
|
-
var MAVEN_TIMEOUT_MS = 5 * 6e4;
|
|
2639
|
-
var ScratchDirectory = class {
|
|
2640
|
-
path;
|
|
2641
|
-
target;
|
|
2642
|
-
createdTarget;
|
|
2643
|
-
constructor(moduleDir) {
|
|
2644
|
-
this.target = join18(moduleDir, "target");
|
|
2645
|
-
this.createdTarget = !existsSync14(this.target);
|
|
2646
|
-
this.path = join18(this.target, `.elevate-${randomUUID().slice(0, 8)}`);
|
|
2647
|
-
mkdirSync(this.path, { recursive: true });
|
|
2648
|
-
}
|
|
2649
|
-
dispose() {
|
|
2650
|
-
rmSync(this.path, { recursive: true, force: true });
|
|
2651
|
-
try {
|
|
2652
|
-
if (this.createdTarget && readdirSync3(this.target).length === 0) rmSync(this.target, { recursive: true });
|
|
2653
|
-
} catch {
|
|
2654
|
-
}
|
|
2655
|
-
}
|
|
2656
|
-
};
|
|
2657
|
-
async function readEffectivePoms(pomFile, moduleDir, rootDir, scratch, plugins, recursive = false) {
|
|
2658
|
-
const output = join18(scratch.path, "effective-pom.xml");
|
|
2659
|
-
const args = [
|
|
2660
|
-
"-f",
|
|
2661
|
-
relativeToCwd(moduleDir, pomFile),
|
|
2662
|
-
`${HELP_PLUGIN_ID}:${plugins.help}:effective-pom`,
|
|
2663
|
-
`-Doutput=${relativeToCwd(moduleDir, output)}`
|
|
2664
|
-
];
|
|
2665
|
-
const result = await runMaven(recursive ? args : ["-N", ...args], {
|
|
2666
|
-
cwd: moduleDir,
|
|
2667
|
-
rootDir,
|
|
2668
|
-
timeoutMs: MAVEN_TIMEOUT_MS
|
|
2669
|
-
});
|
|
2670
|
-
if (result.exitCode !== 0 || !existsSync14(output)) {
|
|
2671
|
-
const text = `${result.stdout}
|
|
2672
|
-
${result.stderr}`;
|
|
2673
|
-
if (isPluginUnavailable(text, HELP_PLUGIN_ID)) {
|
|
2674
|
-
throw new MavenPluginUnavailableError(
|
|
2675
|
-
{ id: HELP_PLUGIN_ID, version: plugins.help, key: "help", purpose: "read the effective POM" },
|
|
2676
|
-
text
|
|
2677
|
-
);
|
|
2678
|
-
}
|
|
2679
|
-
throw new MavenCommandError(`Maven could not build the effective POM:
|
|
2680
|
-
${mavenErrors(text)}`, text);
|
|
2681
|
-
}
|
|
2682
|
-
return parseEffectivePoms(readFileSync14(output, "utf8"));
|
|
2683
|
-
}
|
|
2684
|
-
function parseEffectivePoms(source) {
|
|
2685
|
-
const root = parseXml(source);
|
|
2686
|
-
const projects = root.name === "projects" ? children(root, "project") : [root];
|
|
2687
|
-
const models = /* @__PURE__ */ new Map();
|
|
2688
|
-
for (const project of projects) {
|
|
2689
|
-
models.set(`${childText(project, "groupId")}:${childText(project, "artifactId")}`, toEffectiveModel(project));
|
|
2690
|
-
}
|
|
2691
|
-
return models;
|
|
2692
|
-
}
|
|
2693
|
-
function mergeRepositories(models) {
|
|
2694
|
-
const merge2 = (name, pick) => {
|
|
2695
|
-
const byId = /* @__PURE__ */ new Map();
|
|
2696
|
-
for (const element of models.map(pick)) {
|
|
2697
|
-
for (const repository of element?.children ?? []) {
|
|
2698
|
-
const id = childText(repository, "id") ?? childText(repository, "url") ?? "";
|
|
2699
|
-
if (!byId.has(id)) byId.set(id, repository);
|
|
2700
|
-
}
|
|
2701
|
-
}
|
|
2702
|
-
return byId.size > 0 ? { name, attributes: {}, children: [...byId.values()], text: "" } : void 0;
|
|
2703
|
-
};
|
|
2704
|
-
return {
|
|
2705
|
-
dependencies: [],
|
|
2706
|
-
managedDependencies: [],
|
|
2707
|
-
repositories: merge2("repositories", (m) => m.repositories),
|
|
2708
|
-
pluginRepositories: merge2("pluginRepositories", (m) => m.pluginRepositories)
|
|
2709
|
-
};
|
|
2710
|
-
}
|
|
2711
|
-
function toEffectiveModel(project) {
|
|
2712
|
-
return {
|
|
2713
|
-
dependencies: readEffectiveDependencies(child(project, "dependencies")),
|
|
2714
|
-
managedDependencies: readEffectiveDependencies(childAt(project, "dependencyManagement", "dependencies")),
|
|
2715
|
-
repositories: child(project, "repositories"),
|
|
2716
|
-
pluginRepositories: child(project, "pluginRepositories")
|
|
2717
|
-
};
|
|
2718
|
-
}
|
|
2719
|
-
function readEffectiveDependencies(container) {
|
|
2720
|
-
return children(container, "dependency").map((element) => ({
|
|
2721
|
-
groupId: childText(element, "groupId") ?? "",
|
|
2722
|
-
artifactId: childText(element, "artifactId") ?? "",
|
|
2723
|
-
version: childText(element, "version") ?? "",
|
|
2724
|
-
scope: childText(element, "scope") ?? "compile",
|
|
2725
|
-
type: childText(element, "type") ?? "jar",
|
|
2726
|
-
classifier: childText(element, "classifier")
|
|
2727
|
-
}));
|
|
2728
|
-
}
|
|
2729
|
-
function buildProbePom(artifacts, model) {
|
|
2730
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2731
|
-
const entries2 = [];
|
|
2732
|
-
for (const artifact of artifacts) {
|
|
2733
|
-
const key = `${artifact.groupId}:${artifact.artifactId}:${artifact.type ?? "jar"}:${artifact.classifier ?? ""}`;
|
|
2734
|
-
if (seen.has(key)) continue;
|
|
2735
|
-
seen.add(key);
|
|
2736
|
-
const fields = [
|
|
2737
|
-
`<groupId>${escapeXml(artifact.groupId)}</groupId>`,
|
|
2738
|
-
`<artifactId>${escapeXml(artifact.artifactId)}</artifactId>`,
|
|
2739
|
-
`<version>${escapeXml(artifact.version)}</version>`,
|
|
2740
|
-
artifact.type && artifact.type !== "jar" ? `<type>${escapeXml(artifact.type)}</type>` : "",
|
|
2741
|
-
artifact.classifier ? `<classifier>${escapeXml(artifact.classifier)}</classifier>` : ""
|
|
2742
|
-
].filter(Boolean);
|
|
2743
|
-
entries2.push(` <dependency>${fields.join("")}</dependency>`);
|
|
2744
|
-
}
|
|
2745
|
-
const repositories = [model.repositories, model.pluginRepositories].filter((element) => element !== void 0).map((element) => serializeXml(element, " ")).join("\n");
|
|
2746
|
-
return [
|
|
2747
|
-
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
2748
|
-
'<project xmlns="http://maven.apache.org/POM/4.0.0">',
|
|
2749
|
-
" <modelVersion>4.0.0</modelVersion>",
|
|
2750
|
-
" <groupId>elevate.probe</groupId>",
|
|
2751
|
-
" <artifactId>elevate-probe</artifactId>",
|
|
2752
|
-
" <version>0</version>",
|
|
2753
|
-
" <packaging>pom</packaging>",
|
|
2754
|
-
repositories,
|
|
2755
|
-
" <dependencyManagement>",
|
|
2756
|
-
" <dependencies>",
|
|
2757
|
-
...entries2,
|
|
2758
|
-
" </dependencies>",
|
|
2759
|
-
" </dependencyManagement>",
|
|
2760
|
-
"</project>",
|
|
2761
|
-
""
|
|
2762
|
-
].filter((line) => line !== "").join("\n");
|
|
2763
|
-
}
|
|
2764
|
-
async function queryNewerVersions(artifacts, model, moduleDir, rootDir, scratch, plugins) {
|
|
2765
|
-
if (artifacts.length === 0) return { newerVersions: /* @__PURE__ */ new Map(), failedLookups: /* @__PURE__ */ new Set() };
|
|
2766
|
-
const probeDir = join18(scratch.path, "probe");
|
|
2767
|
-
mkdirSync(probeDir, { recursive: true });
|
|
2768
|
-
const probePom = join18(probeDir, "pom.xml");
|
|
2769
|
-
writeFileSync3(probePom, buildProbePom(artifacts, model), "utf8");
|
|
2770
|
-
const result = await runMaven(
|
|
2771
|
-
[
|
|
2772
|
-
"-N",
|
|
2773
|
-
"-f",
|
|
2774
|
-
relativeToCwd(moduleDir, probePom),
|
|
2775
|
-
`${VERSIONS_PLUGIN_ID}:${plugins.versions}:dependency-updates-report`,
|
|
2776
|
-
"-DdependencyUpdatesReportFormats=xml",
|
|
2777
|
-
"-DprocessDependencyManagementTransitive=false"
|
|
2778
|
-
],
|
|
2779
|
-
{ cwd: moduleDir, rootDir, timeoutMs: MAVEN_TIMEOUT_MS }
|
|
2780
|
-
);
|
|
2781
|
-
const log = `${result.stdout}
|
|
2782
|
-
${result.stderr}`;
|
|
2783
|
-
const reportFile = join18(probeDir, "target", "dependency-updates-report.xml");
|
|
2784
|
-
if (result.exitCode !== 0 || !existsSync14(reportFile)) {
|
|
2785
|
-
if (isPluginUnavailable(log, VERSIONS_PLUGIN_ID)) {
|
|
2786
|
-
throw new MavenPluginUnavailableError(
|
|
2787
|
-
{ id: VERSIONS_PLUGIN_ID, version: plugins.versions, key: "versions", purpose: "look up newer versions" },
|
|
2788
|
-
log
|
|
2789
|
-
);
|
|
2790
|
-
}
|
|
2791
|
-
throw new MavenCommandError(`Maven could not look up newer versions:
|
|
2792
|
-
${mavenErrors(log)}`, log);
|
|
2793
|
-
}
|
|
2794
|
-
return {
|
|
2795
|
-
newerVersions: parseUpdatesReport(readFileSync14(reportFile, "utf8")),
|
|
2796
|
-
failedLookups: parseFailedLookups(log)
|
|
2797
|
-
};
|
|
2798
|
-
}
|
|
2799
|
-
function parseUpdatesReport(source) {
|
|
2800
|
-
const report = parseXml(source);
|
|
2801
|
-
const sections = [
|
|
2802
|
-
...children(child(report, "dependencyManagements"), "dependencyManagement"),
|
|
2803
|
-
...children(child(report, "dependencies"), "dependency")
|
|
2804
|
-
];
|
|
2805
|
-
const result = /* @__PURE__ */ new Map();
|
|
2806
|
-
for (const entry of sections) {
|
|
2807
|
-
const key = `${childText(entry, "groupId")}:${childText(entry, "artifactId")}`;
|
|
2808
|
-
const versions = [
|
|
2809
|
-
...collect(entry, "subIncrementals", "subIncremental"),
|
|
2810
|
-
...collect(entry, "incrementals", "incremental"),
|
|
2811
|
-
...collect(entry, "minors", "minor"),
|
|
2812
|
-
...collect(entry, "majors", "major")
|
|
2813
|
-
];
|
|
2814
|
-
const known = result.get(key) ?? [];
|
|
2815
|
-
for (const version of versions) if (!known.includes(version)) known.push(version);
|
|
2816
|
-
result.set(key, known);
|
|
2817
|
-
}
|
|
2818
|
-
return result;
|
|
2819
|
-
}
|
|
2820
|
-
function collect(entry, container, item) {
|
|
2821
|
-
return children(child(entry, container), item).map((element) => element.text.trim()).filter(Boolean);
|
|
2822
|
-
}
|
|
2823
|
-
function parseFailedLookups(log) {
|
|
2824
|
-
const failed = /* @__PURE__ */ new Set();
|
|
2825
|
-
const pattern = /Could not (?:transfer|read) metadata ([^:\s/]+):([^:\s/]+)(?::[^\s/]+)?\/maven-metadata\.xml/g;
|
|
2826
|
-
for (const match of log.matchAll(pattern)) failed.add(`${match[1]}:${match[2]}`);
|
|
2827
|
-
return failed;
|
|
2828
|
-
}
|
|
2829
|
-
|
|
2830
|
-
// src/adapters/maven/maven-registry.ts
|
|
2831
|
-
var PROBE_VERSION = "0";
|
|
2832
|
-
var NO_REPOSITORIES = { dependencies: [], managedDependencies: [] };
|
|
2833
|
-
var MavenRegistryAdapter = class {
|
|
2834
|
-
constructor(plugins = DEFAULT_MAVEN_PLUGINS) {
|
|
2835
|
-
this.plugins = plugins;
|
|
2836
|
-
}
|
|
2837
|
-
plugins;
|
|
2838
|
-
/**
|
|
2839
|
-
* The repositories come from Maven's own configuration, which Elevate does
|
|
2840
|
-
* not interpret. They are reported as non-public: the lookup goes exactly
|
|
2841
|
-
* where the project's build would go.
|
|
2842
|
-
*/
|
|
2843
|
-
async resolveEndpoint() {
|
|
2844
|
-
return { url: "repositories configured for Maven (settings.xml, POM)", isPublic: false };
|
|
2845
|
-
}
|
|
2846
|
-
async getLatestVersion(coordinate, channel = "stable", cwd = process.cwd()) {
|
|
2847
|
-
const versions = await this.getAllVersions(coordinate, cwd);
|
|
2848
|
-
return versions.find((v) => channel === "all" || !isPreReleaseVersion(v)) ?? null;
|
|
2849
|
-
}
|
|
2850
|
-
/**
|
|
2851
|
-
* All versions available in the configured repositories, newest first.
|
|
2852
|
-
* Throws `MavenCommandError` when Maven fails or a repository could not be
|
|
2853
|
-
* read, so an error is never mistaken for "no versions".
|
|
2854
|
-
*/
|
|
2855
|
-
async getAllVersions(coordinate, cwd = process.cwd()) {
|
|
2856
|
-
if (!coordinate.group || !coordinate.artifact) return [];
|
|
2857
|
-
const key = `${coordinate.group}:${coordinate.artifact}`;
|
|
2858
|
-
const scratch = new ScratchDirectory(cwd);
|
|
2859
|
-
try {
|
|
2860
|
-
const repositories = await projectRepositories(cwd, scratch, this.plugins);
|
|
2861
|
-
const report = await queryNewerVersions(
|
|
2862
|
-
[{ groupId: coordinate.group, artifactId: coordinate.artifact, version: PROBE_VERSION }],
|
|
2863
|
-
repositories,
|
|
2864
|
-
cwd,
|
|
2865
|
-
cwd,
|
|
2866
|
-
scratch,
|
|
2867
|
-
this.plugins
|
|
2868
|
-
);
|
|
2869
|
-
const versions = report.newerVersions.get(key) ?? [];
|
|
2870
|
-
if (versions.length === 0 && report.failedLookups.has(key)) {
|
|
2871
|
-
throw new MavenCommandError(`A Maven repository failed to deliver the versions of ${key}.`, "");
|
|
2872
|
-
}
|
|
2873
|
-
return [...versions].reverse();
|
|
2874
|
-
} finally {
|
|
2875
|
-
scratch.dispose();
|
|
2876
|
-
}
|
|
2877
|
-
}
|
|
2878
|
-
};
|
|
2879
|
-
async function projectRepositories(cwd, scratch, plugins) {
|
|
2880
|
-
const rootPom = join19(cwd, "pom.xml");
|
|
2881
|
-
if (!existsSync15(rootPom)) return NO_REPOSITORIES;
|
|
2882
|
-
const models = await readEffectivePoms(rootPom, cwd, cwd, scratch, plugins);
|
|
2883
|
-
return [...models.values()][0] ?? NO_REPOSITORIES;
|
|
2884
|
-
}
|
|
2885
|
-
|
|
2886
|
-
// src/adapters/maven/maven-scanner.ts
|
|
2887
|
-
import { join as join20 } from "path";
|
|
2888
|
-
import semver11 from "semver";
|
|
2889
|
-
|
|
2890
|
-
// src/adapters/maven/maven-locator.ts
|
|
2891
|
-
import { relative as relative8 } from "path";
|
|
2892
|
-
function locateDependencyVersion(chain, target, rootDir) {
|
|
2893
|
-
for (const pom of chain) {
|
|
2894
|
-
const dependency = findDependency(pom.dependencies, target, chain);
|
|
2895
|
-
if (!dependency) continue;
|
|
2896
|
-
if (!dependency.versionElement) break;
|
|
2897
|
-
return resolveLiteral(pom, "dependency", dependency.versionElement, chain, target, rootDir);
|
|
2898
|
-
}
|
|
2899
|
-
return locateManagedVersion(chain, target, rootDir);
|
|
2900
|
-
}
|
|
2901
|
-
function locateManagedVersion(chain, target, rootDir) {
|
|
2902
|
-
for (const pom of chain) {
|
|
2903
|
-
const managed = findDependency(pom.managedDependencies, target, chain);
|
|
2904
|
-
if (managed?.versionElement) {
|
|
2905
|
-
return resolveLiteral(pom, "dependency-management", managed.versionElement, chain, target, rootDir);
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
2908
|
-
return {
|
|
2909
|
-
found: false,
|
|
2910
|
-
reason: "managed-externally",
|
|
2911
|
-
detail: "version is managed outside this repository (external parent POM or imported BOM)"
|
|
2912
|
-
};
|
|
2913
|
-
}
|
|
2914
|
-
function locateParentVersion(module, target, rootDir) {
|
|
2915
|
-
const element = module.parent?.versionElement;
|
|
2916
|
-
if (!element) {
|
|
2917
|
-
return { found: false, reason: "declaration-mismatch", detail: "parent has no <version> element" };
|
|
2918
|
-
}
|
|
2919
|
-
return resolveLiteral(module, "parent", element, [module], target, rootDir);
|
|
2920
|
-
}
|
|
2921
|
-
function findDependency(dependencies, target, chain) {
|
|
2922
|
-
return dependencies.find(
|
|
2923
|
-
(dep) => dep.scope !== "import" && interpolate(dep.groupId, chain) === target.groupId && interpolate(dep.artifactId, chain) === target.artifactId
|
|
2924
|
-
);
|
|
2925
|
-
}
|
|
2926
|
-
function resolveLiteral(pom, kind, versionElement, chain, target, rootDir) {
|
|
2927
|
-
let file = pom.file;
|
|
2928
|
-
let element = versionElement;
|
|
2929
|
-
let value = versionElement.text.trim();
|
|
2930
|
-
let propertyName;
|
|
2931
|
-
for (let depth = 0; depth < 10; depth++) {
|
|
2932
|
-
const reference = propertyReference(value);
|
|
2933
|
-
if (!reference) break;
|
|
2934
|
-
if (reference.startsWith("project.") || reference.startsWith("pom.")) {
|
|
2935
|
-
return {
|
|
2936
|
-
found: false,
|
|
2937
|
-
reason: "declaration-mismatch",
|
|
2938
|
-
detail: `version is the expression \${${reference}}, not an editable value`
|
|
2939
|
-
};
|
|
2940
|
-
}
|
|
2941
|
-
const definition = findProperty(reference, chain);
|
|
2942
|
-
if (!definition) {
|
|
2943
|
-
return {
|
|
2944
|
-
found: false,
|
|
2945
|
-
reason: "managed-externally",
|
|
2946
|
-
detail: `property \${${reference}} is defined outside this repository`
|
|
2947
|
-
};
|
|
2948
|
-
}
|
|
2949
|
-
file = definition.pom.file;
|
|
2950
|
-
element = definition.property.element;
|
|
2951
|
-
value = definition.property.value;
|
|
2952
|
-
propertyName = reference;
|
|
2953
|
-
}
|
|
2954
|
-
if (value.includes("${")) {
|
|
2955
|
-
return {
|
|
2956
|
-
found: false,
|
|
2957
|
-
reason: "declaration-mismatch",
|
|
2958
|
-
detail: `version '${value}' combines several expressions`
|
|
2959
|
-
};
|
|
2960
|
-
}
|
|
2961
|
-
if (value !== target.effectiveVersion) {
|
|
2962
|
-
return {
|
|
2963
|
-
found: false,
|
|
2964
|
-
reason: "declaration-mismatch",
|
|
2965
|
-
detail: `declared '${value}' but Maven resolves '${target.effectiveVersion}' (profile or command-line override?)`
|
|
2966
|
-
};
|
|
2967
|
-
}
|
|
2968
|
-
const declaration = {
|
|
2969
|
-
file,
|
|
2970
|
-
kind: propertyName ? "property" : kind,
|
|
2971
|
-
propertyName,
|
|
2972
|
-
displayPath: relative8(rootDir, file).replace(/\\/g, "/")
|
|
2973
|
-
};
|
|
2974
|
-
return { found: true, declaration, element, value };
|
|
2975
|
-
}
|
|
2976
|
-
|
|
2977
|
-
// src/adapters/maven/maven-scanner.ts
|
|
2978
|
-
var DIFF_ORDER3 = { major: 0, minor: 1, patch: 2 };
|
|
2979
|
-
var MavenDependencyAdapter = class {
|
|
2980
|
-
constructor(plugins = DEFAULT_MAVEN_PLUGINS) {
|
|
2981
|
-
this.plugins = plugins;
|
|
2982
|
-
}
|
|
2983
|
-
plugins;
|
|
2984
|
-
async scan(module, context) {
|
|
2985
|
-
const results = await this.scanMany([module], context);
|
|
2986
|
-
return results.get(module) ?? { candidates: [], skipped: [] };
|
|
2987
|
-
}
|
|
2988
|
-
async scanMany(modules, context) {
|
|
2989
|
-
const project = MavenProject.load(context.rootDir);
|
|
2990
|
-
const results = /* @__PURE__ */ new Map();
|
|
2991
|
-
for (const group of groupModules(project, modules)) {
|
|
2992
|
-
const scratch = new ScratchDirectory(group.dir);
|
|
2993
|
-
try {
|
|
2994
|
-
for (const [module, result] of await this.scanGroup(project, group, context, scratch)) {
|
|
2995
|
-
results.set(module, result);
|
|
2996
|
-
}
|
|
2997
|
-
} finally {
|
|
2998
|
-
scratch.dispose();
|
|
2999
|
-
}
|
|
3000
|
-
}
|
|
3001
|
-
return results;
|
|
3002
|
-
}
|
|
3003
|
-
async scanGroup(project, group, context, scratch) {
|
|
3004
|
-
const rootDir = context.rootDir;
|
|
3005
|
-
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);
|
|
3006
|
-
const plans = [];
|
|
3007
|
-
for (const { module, pom } of group.members) {
|
|
3008
|
-
const coordinates = project.coordinatesOf(pom);
|
|
3009
|
-
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]);
|
|
3010
|
-
if (!effective) continue;
|
|
3011
|
-
plans.push(planModule(project, pom, module, effective, context));
|
|
3012
|
-
}
|
|
3013
|
-
const probe = plans.flatMap((plan) => plan.lookups.map(({ subject }) => subject));
|
|
3014
|
-
const report = await queryNewerVersions(
|
|
3015
|
-
probe,
|
|
3016
|
-
mergeRepositories(plans.map((plan) => plan.effective)),
|
|
3017
|
-
group.dir,
|
|
3018
|
-
rootDir,
|
|
3019
|
-
scratch,
|
|
3020
|
-
this.plugins
|
|
3021
|
-
);
|
|
3022
|
-
return new Map(plans.map((plan) => [plan.module, finishModule(plan, report, context.channel)]));
|
|
3023
|
-
}
|
|
3024
|
-
};
|
|
3025
|
-
function groupModules(project, modules) {
|
|
3026
|
-
const groups = /* @__PURE__ */ new Map();
|
|
3027
|
-
const reactorSizes = /* @__PURE__ */ new Map();
|
|
3028
|
-
for (const module of modules) {
|
|
3029
|
-
if (module.aggregatorDir) reactorSizes.set(module.aggregatorDir, (reactorSizes.get(module.aggregatorDir) ?? 0) + 1);
|
|
3030
|
-
}
|
|
3031
|
-
for (const module of modules) {
|
|
3032
|
-
const pom = project.pomAt(join20(module.path, "pom.xml"));
|
|
3033
|
-
if (!pom) continue;
|
|
3034
|
-
const reactor = module.aggregatorDir;
|
|
3035
|
-
const batched = reactor !== void 0 && (reactorSizes.get(reactor) ?? 0) > 1;
|
|
3036
|
-
const key = batched ? `reactor:${reactor}` : `module:${module.path}`;
|
|
3037
|
-
const group = groups.get(key) ?? {
|
|
3038
|
-
dir: batched ? reactor : module.path,
|
|
3039
|
-
aggregatorPom: batched ? join20(reactor, "pom.xml") : void 0,
|
|
3040
|
-
members: []
|
|
3041
|
-
};
|
|
3042
|
-
group.members.push({ module, pom });
|
|
3043
|
-
groups.set(key, group);
|
|
3044
|
-
}
|
|
3045
|
-
return [...groups.values()];
|
|
3046
|
-
}
|
|
3047
|
-
function planModule(project, pom, module, effective, context) {
|
|
3048
|
-
const { poms: chain, externalParent } = project.chainOf(pom);
|
|
3049
|
-
const rootDir = context.rootDir;
|
|
3050
|
-
const subjects = /* @__PURE__ */ new Map();
|
|
3051
|
-
for (const dep of effective.dependencies) {
|
|
3052
|
-
const key = `${dep.groupId}:${dep.artifactId}`;
|
|
3053
|
-
if (subjects.has(key)) continue;
|
|
3054
|
-
const target = { groupId: dep.groupId, artifactId: dep.artifactId };
|
|
3055
|
-
subjects.set(key, {
|
|
3056
|
-
...dep,
|
|
3057
|
-
scope: dep.scope === "test" ? "test" : "prod",
|
|
3058
|
-
locate: (effectiveVersion) => locateDependencyVersion(chain, { ...target, effectiveVersion }, rootDir)
|
|
3059
|
-
});
|
|
3060
|
-
}
|
|
3061
|
-
for (const managed of pom.managedDependencies) {
|
|
3062
|
-
const groupId = interpolate(managed.groupId, chain);
|
|
3063
|
-
const artifactId = interpolate(managed.artifactId, chain);
|
|
3064
|
-
const key = `${groupId}:${artifactId}`;
|
|
3065
|
-
if (subjects.has(key) || !managed.version) continue;
|
|
3066
|
-
const isImport = managed.scope === "import";
|
|
3067
|
-
const version = isImport ? interpolate(managed.version, chain) : effective.managedDependencies.find((m) => m.groupId === groupId && m.artifactId === artifactId)?.version;
|
|
3068
|
-
if (!version || version.includes("${")) continue;
|
|
3069
|
-
const target = { groupId, artifactId };
|
|
3070
|
-
subjects.set(key, {
|
|
3071
|
-
groupId,
|
|
3072
|
-
artifactId,
|
|
3073
|
-
version,
|
|
3074
|
-
type: isImport ? "pom" : managed.type,
|
|
3075
|
-
classifier: managed.classifier,
|
|
3076
|
-
scope: "prod",
|
|
3077
|
-
locate: (effectiveVersion) => locateManagedVersion(chain, { ...target, effectiveVersion }, rootDir)
|
|
3078
|
-
});
|
|
3079
|
-
}
|
|
3080
|
-
if (pom.parent && externalParent === pom.parent) {
|
|
3081
|
-
const { groupId, artifactId } = pom.parent;
|
|
3082
|
-
const version = interpolate(pom.parent.version, chain);
|
|
3083
|
-
subjects.set(`${groupId}:${artifactId}`, {
|
|
3084
|
-
groupId,
|
|
3085
|
-
artifactId,
|
|
3086
|
-
version,
|
|
3087
|
-
type: "pom",
|
|
3088
|
-
scope: "prod",
|
|
3089
|
-
locate: (effectiveVersion) => locateParentVersion(pom, { groupId, artifactId, effectiveVersion }, rootDir)
|
|
3090
|
-
});
|
|
3091
|
-
}
|
|
3092
|
-
const plan = { module, effective, candidates: [], skipped: [], lookups: [] };
|
|
3093
|
-
for (const [key, subject] of subjects) {
|
|
3094
|
-
const origin = classifyOrigin(key, "maven", context);
|
|
3095
|
-
if (origin.kind !== "workspace") {
|
|
3096
|
-
plan.lookups.push({ subject, origin });
|
|
3097
|
-
continue;
|
|
3098
|
-
}
|
|
3099
|
-
const target = context.workspace.get(key);
|
|
3100
|
-
if (target && inSameReactor(module, target)) {
|
|
3101
|
-
if (target.version && target.version !== subject.version) {
|
|
3102
|
-
collect2(alignmentCandidate2(subject, target.version, origin), plan.candidates, plan.skipped);
|
|
3103
|
-
}
|
|
3104
|
-
} else {
|
|
3105
|
-
plan.lookups.push({ subject, origin: classifyOrigin(key, "maven", { ...context, workspace: /* @__PURE__ */ new Map() }) });
|
|
3106
|
-
}
|
|
3107
|
-
}
|
|
3108
|
-
return plan;
|
|
3109
|
-
}
|
|
3110
|
-
function inSameReactor(a, b) {
|
|
3111
|
-
return a.aggregatorDir !== void 0 && a.aggregatorDir === b.aggregatorDir;
|
|
3112
|
-
}
|
|
3113
|
-
function finishModule(plan, report, channel) {
|
|
3114
|
-
const { candidates, skipped } = plan;
|
|
3115
|
-
for (const { subject, origin } of plan.lookups) {
|
|
3116
|
-
const key = `${subject.groupId}:${subject.artifactId}`;
|
|
3117
|
-
const newer = newerThan(subject.version, report.newerVersions.get(key) ?? []);
|
|
3118
|
-
const latest = pickLatest(subject.version, newer, channel);
|
|
3119
|
-
if (!latest) {
|
|
3120
|
-
if (report.failedLookups.has(key)) {
|
|
3121
|
-
skipped.push({ identifier: key, origin: origin.kind, reason: "lookup-failed", detail: "repository error" });
|
|
3122
|
-
}
|
|
3123
|
-
continue;
|
|
3124
|
-
}
|
|
3125
|
-
collect2(updateCandidate(subject, latest, newer, origin), candidates, skipped);
|
|
3126
|
-
}
|
|
3127
|
-
markSharedDeclarations(candidates);
|
|
3128
|
-
candidates.sort((a, b) => (DIFF_ORDER3[a.diff] ?? 3) - (DIFF_ORDER3[b.diff] ?? 3));
|
|
3129
|
-
return { candidates, skipped };
|
|
3130
|
-
}
|
|
3131
|
-
function newerThan(current, ascending) {
|
|
3132
|
-
const index = ascending.indexOf(current);
|
|
3133
|
-
return index >= 0 ? ascending.slice(index + 1) : [...ascending];
|
|
3134
|
-
}
|
|
3135
|
-
function collect2(built, candidates, skipped) {
|
|
3136
|
-
if ("candidate" in built) candidates.push(built.candidate);
|
|
3137
|
-
else skipped.push(built.skipped);
|
|
3138
|
-
}
|
|
3139
|
-
function updateCandidate(subject, latest, newer, origin) {
|
|
3140
|
-
const base = buildCandidate(subject, latest, origin, "update");
|
|
3141
|
-
if ("candidate" in base) {
|
|
3142
|
-
base.candidate.availableVersions = [...newer].reverse().concat(subject.version);
|
|
3143
|
-
}
|
|
3144
|
-
return base;
|
|
3145
|
-
}
|
|
3146
|
-
function alignmentCandidate2(subject, localVersion, origin) {
|
|
3147
|
-
const built = buildCandidate(subject, localVersion, origin, "align");
|
|
3148
|
-
if ("candidate" in built) built.candidate.availableVersions = [localVersion];
|
|
3149
|
-
return built;
|
|
3150
|
-
}
|
|
3151
|
-
function buildCandidate(subject, target, origin, action) {
|
|
3152
|
-
const identifier = `${subject.groupId}:${subject.artifactId}`;
|
|
3153
|
-
const location = subject.locate(subject.version);
|
|
3154
|
-
if (!location.found) {
|
|
3155
|
-
return { skipped: { identifier, origin: origin.kind, reason: location.reason, detail: location.detail } };
|
|
3156
|
-
}
|
|
3157
|
-
const currentClean = cleanJavaVersion(subject.version) ?? "0.0.0";
|
|
3158
|
-
const targetClean = cleanJavaVersion(target) ?? currentClean;
|
|
3159
|
-
const diff = semver11.eq(currentClean, targetClean) ? "patch" : diffVersions(currentClean, targetClean);
|
|
3160
|
-
const isPreRelease = isPreReleaseVersion(target);
|
|
3161
|
-
return {
|
|
3162
|
-
candidate: {
|
|
3163
|
-
coordinate: { identifier, group: subject.groupId, artifact: subject.artifactId, ecosystem: "maven" },
|
|
3164
|
-
currentRange: subject.version,
|
|
3165
|
-
currentClean,
|
|
3166
|
-
latest: target,
|
|
3167
|
-
newRange: target,
|
|
3168
|
-
diff,
|
|
3169
|
-
scope: subject.scope,
|
|
3170
|
-
selected: diff !== "major",
|
|
3171
|
-
origin,
|
|
3172
|
-
action,
|
|
3173
|
-
declaration: location.declaration,
|
|
3174
|
-
isPreRelease,
|
|
3175
|
-
preReleaseTag: isPreRelease ? extractPreReleaseTag(target) : void 0
|
|
3176
|
-
}
|
|
3177
|
-
};
|
|
3178
|
-
}
|
|
3179
|
-
function pickLatest(current, newer, channel) {
|
|
3180
|
-
const allowed = newer.filter((v) => channel === "all" || !isPreReleaseVersion(v));
|
|
3181
|
-
const flavour = flavourOf(current);
|
|
3182
|
-
const sameFlavour = flavour ? allowed.filter((v) => flavourOf(v) === flavour) : [];
|
|
3183
|
-
const pool = sameFlavour.length > 0 ? sameFlavour : allowed;
|
|
3184
|
-
return pool[pool.length - 1];
|
|
3185
|
-
}
|
|
3186
|
-
function flavourOf(version) {
|
|
3187
|
-
const suffix = version.match(/-([A-Za-z]+)$/)?.[1];
|
|
3188
|
-
return suffix && !isPreReleaseVersion(version) ? suffix.toLowerCase() : void 0;
|
|
3189
|
-
}
|
|
3190
|
-
function markSharedDeclarations(candidates) {
|
|
3191
|
-
const groups = /* @__PURE__ */ new Map();
|
|
3192
|
-
for (const candidate of candidates) {
|
|
3193
|
-
const declaration = candidate.declaration;
|
|
3194
|
-
if (declaration?.kind !== "property") continue;
|
|
3195
|
-
const key = `${declaration.file}#${declaration.propertyName}`;
|
|
3196
|
-
groups.set(key, [...groups.get(key) ?? [], candidate]);
|
|
3197
|
-
}
|
|
3198
|
-
for (const group of groups.values()) {
|
|
3199
|
-
if (group.length < 2) continue;
|
|
3200
|
-
for (const candidate of group) {
|
|
3201
|
-
candidate.sharedWith = group.filter((other) => other !== candidate).map((other) => other.coordinate.identifier);
|
|
3202
|
-
}
|
|
3203
|
-
}
|
|
3204
|
-
}
|
|
3205
|
-
|
|
3206
|
-
// src/adapters/maven/maven-updater.ts
|
|
3207
|
-
import { readFileSync as readFileSync15, writeFileSync as writeFileSync4 } from "fs";
|
|
3208
|
-
import { join as join21, resolve as resolve12 } from "path";
|
|
3209
|
-
var MavenUpdaterAdapter = class {
|
|
3210
|
-
affectedFiles(module, _rootDir, updates) {
|
|
3211
|
-
const files = new Set(updates.map((u) => resolve12(u.declaration?.file ?? join21(module.path, "pom.xml"))));
|
|
3212
|
-
return [...files];
|
|
3213
|
-
}
|
|
3214
|
-
async applyUpdates(module, rootDir, updates, onProgress) {
|
|
3215
|
-
const project = MavenProject.load(rootDir);
|
|
3216
|
-
const byFile = /* @__PURE__ */ new Map();
|
|
3217
|
-
for (const update of updates) {
|
|
3218
|
-
if (!update.declaration) {
|
|
3219
|
-
throw new InstallError(`No version declaration known for ${update.coordinate.identifier}; rescan the module.`);
|
|
3220
|
-
}
|
|
3221
|
-
const file = resolve12(update.declaration.file);
|
|
3222
|
-
byFile.set(file, [...byFile.get(file) ?? [], update]);
|
|
3223
|
-
}
|
|
3224
|
-
const writes = [];
|
|
3225
|
-
for (const [file, fileUpdates] of byFile) {
|
|
3226
|
-
onProgress(`Preparing ${fileUpdates.length} change(s) in ${fileUpdates[0].declaration.displayPath}\u2026`);
|
|
3227
|
-
const source = readFileSync15(file, "utf8");
|
|
3228
|
-
const pom = parsePom(source, file);
|
|
3229
|
-
const chain = project.pomAt(file) ? project.chainOf(project.pomAt(file)).poms : [pom];
|
|
3230
|
-
const edits = collectEdits(pom, [pom, ...chain.slice(1)], fileUpdates);
|
|
3231
|
-
try {
|
|
3232
|
-
writes.push({ file, content: applyTextEdits(source, edits) });
|
|
3233
|
-
} catch (err) {
|
|
3234
|
-
if (err instanceof StaleEditError) throw new InstallError(`${file}: ${err.message}`);
|
|
3235
|
-
throw err;
|
|
3236
|
-
}
|
|
3237
|
-
}
|
|
3238
|
-
onProgress("Writing POM files\u2026");
|
|
3239
|
-
for (const write of writes) writeFileSync4(write.file, write.content, "utf8");
|
|
3240
|
-
return {
|
|
3241
|
-
updatedCount: updates.length,
|
|
3242
|
-
auditMessage: `Updated ${updates.length} version declaration(s) in ${writes.length} POM file(s).`,
|
|
3243
|
-
auditSeverity: "clean",
|
|
3244
|
-
integrityViolations: []
|
|
3245
|
-
};
|
|
3246
|
-
}
|
|
3247
|
-
async resync() {
|
|
3248
|
-
}
|
|
3249
|
-
};
|
|
3250
|
-
function collectEdits(pom, chain, updates) {
|
|
3251
|
-
const edits = /* @__PURE__ */ new Map();
|
|
3252
|
-
for (const update of updates) {
|
|
3253
|
-
const element = findDeclarationElement(pom, chain, update);
|
|
3254
|
-
if (!element) {
|
|
3255
|
-
throw new InstallError(
|
|
3256
|
-
`${update.declaration.displayPath}: cannot find the version of ${update.coordinate.identifier} any more; rescan the module.`
|
|
3257
|
-
);
|
|
3258
|
-
}
|
|
3259
|
-
const existing = edits.get(element);
|
|
3260
|
-
if (existing) {
|
|
3261
|
-
if (existing.replacement !== update.newRange) {
|
|
3262
|
-
throw new InstallError(
|
|
3263
|
-
`${update.coordinate.identifier} and ${existing.identifier} share ${describe(update.declaration)} but target different versions (${update.newRange} vs ${existing.replacement}).`
|
|
3264
|
-
);
|
|
3265
|
-
}
|
|
3266
|
-
continue;
|
|
3267
|
-
}
|
|
3268
|
-
edits.set(element, {
|
|
3269
|
-
element,
|
|
3270
|
-
expected: update.currentRange,
|
|
3271
|
-
replacement: update.newRange,
|
|
3272
|
-
identifier: update.coordinate.identifier
|
|
3273
|
-
});
|
|
3274
|
-
}
|
|
3275
|
-
return [...edits.values()];
|
|
3276
|
-
}
|
|
3277
|
-
function findDeclarationElement(pom, chain, update) {
|
|
3278
|
-
const declaration = update.declaration;
|
|
3279
|
-
const { group, artifact } = update.coordinate;
|
|
3280
|
-
const matches = (dep) => interpolate(dep.groupId, chain) === group && interpolate(dep.artifactId, chain) === artifact;
|
|
3281
|
-
switch (declaration.kind) {
|
|
3282
|
-
case "property":
|
|
3283
|
-
return declaration.propertyName ? pom.properties.get(declaration.propertyName)?.element : void 0;
|
|
3284
|
-
case "parent":
|
|
3285
|
-
return pom.parent?.versionElement;
|
|
3286
|
-
case "dependency":
|
|
3287
|
-
return pom.dependencies.find(matches)?.versionElement;
|
|
3288
|
-
case "dependency-management":
|
|
3289
|
-
return pom.managedDependencies.find(matches)?.versionElement;
|
|
3290
|
-
}
|
|
3291
|
-
}
|
|
3292
|
-
function describe(declaration) {
|
|
3293
|
-
return declaration.kind === "property" ? `the property \${${declaration.propertyName}} in ${declaration.displayPath}` : `a version in ${declaration.displayPath}`;
|
|
3294
|
-
}
|
|
3295
|
-
|
|
3296
|
-
// src/adapters/maven/maven-verifier.ts
|
|
3297
|
-
import { dirname as dirname9, join as join22, relative as relative9, resolve as resolve13 } from "path";
|
|
3298
|
-
var VERIFY_TIMEOUT_MS3 = 30 * 6e4;
|
|
3299
|
-
var MavenVerificationAdapter = class {
|
|
3300
|
-
async verify(module, rootDir, options = {}) {
|
|
3301
|
-
if (options.customScript) {
|
|
3302
|
-
const label2 = options.customLabel || options.customScript;
|
|
3303
|
-
const result = await runShellScript(options.customScript, { cwd: module.path, timeoutMs: VERIFY_TIMEOUT_MS3 });
|
|
3304
|
-
return result.exitCode === 0 ? { status: "clean", details: "Verification script succeeded.", label: label2 } : { status: "warn", details: failureDetails2(result), label: label2 };
|
|
3305
|
-
}
|
|
3306
|
-
const plan = planBuild(module, rootDir, options.changedFiles ?? []);
|
|
3307
|
-
const label = `Maven build (${["test-compile", ...plan.args].join(" ")})`;
|
|
3308
|
-
try {
|
|
3309
|
-
const result = await runMaven(["-q", "test-compile", ...plan.args], {
|
|
3310
|
-
cwd: plan.cwd,
|
|
3311
|
-
rootDir,
|
|
3312
|
-
timeoutMs: VERIFY_TIMEOUT_MS3
|
|
3313
|
-
});
|
|
3314
|
-
if (result.exitCode === 0) {
|
|
3315
|
-
return { status: "clean", details: "All affected modules compile, including tests.", label };
|
|
3316
|
-
}
|
|
3317
|
-
const details = result.timedOut ? "Maven build timed out." : failureDetails2(result);
|
|
3318
|
-
return { status: "warn", details, label };
|
|
3319
|
-
} catch (err) {
|
|
3320
|
-
if (err instanceof MavenUnavailableError) return { status: "warn", details: err.message, label };
|
|
3321
|
-
throw err;
|
|
3322
|
-
}
|
|
3323
|
-
}
|
|
3324
|
-
};
|
|
3325
|
-
function failureDetails2(result) {
|
|
3326
|
-
const output = `${result.stdout}
|
|
3327
|
-
${result.stderr}`.trim();
|
|
3328
|
-
return output ? mavenErrors(output) : `Exited with code ${result.exitCode}.`;
|
|
3329
|
-
}
|
|
3330
|
-
function planBuild(module, rootDir, changedFiles) {
|
|
3331
|
-
const aggregatorDir = module.aggregatorDir;
|
|
3332
|
-
if (!aggregatorDir) return { cwd: module.path, args: [] };
|
|
3333
|
-
const project = MavenProject.load(rootDir);
|
|
3334
|
-
const files = changedFiles.length > 0 ? changedFiles : [join22(module.path, "pom.xml")];
|
|
3335
|
-
const selected = /* @__PURE__ */ new Set();
|
|
3336
|
-
for (const file of files) {
|
|
3337
|
-
const pom = project.pomAt(file);
|
|
3338
|
-
const dir = resolve13(dirname9(file));
|
|
3339
|
-
if (!pom || project.aggregatorDirOf(pom) !== aggregatorDir || dir === resolve13(aggregatorDir)) {
|
|
3340
|
-
return { cwd: aggregatorDir, args: [] };
|
|
3341
|
-
}
|
|
3342
|
-
selected.add(relative9(aggregatorDir, dir).replace(/\\/g, "/"));
|
|
3343
|
-
}
|
|
3344
|
-
return { cwd: aggregatorDir, args: ["-pl", [...selected].join(","), "-amd"] };
|
|
3345
|
-
}
|
|
3346
|
-
|
|
3347
|
-
// src/adapters/maven/maven-strategy.ts
|
|
3348
|
-
var MavenEcosystemStrategy = class {
|
|
3349
|
-
ecosystem = "maven";
|
|
3350
|
-
displayName = "Java / Maven";
|
|
3351
|
-
icon = "\u2615";
|
|
3352
|
-
manifestFile = "pom.xml";
|
|
3353
|
-
versioning = mavenVersionPolicy;
|
|
3354
|
-
discovery = new MavenModuleDiscoveryAdapter();
|
|
3355
|
-
registry;
|
|
3356
|
-
reader;
|
|
3357
|
-
updater = new MavenUpdaterAdapter();
|
|
3358
|
-
verifier = new MavenVerificationAdapter();
|
|
3359
|
-
/** @param plugins versions of the Maven plugins Elevate runs (default: the pinned ones) */
|
|
3360
|
-
constructor(plugins = DEFAULT_MAVEN_PLUGINS) {
|
|
3361
|
-
this.registry = new MavenRegistryAdapter(plugins);
|
|
3362
|
-
this.reader = new MavenDependencyAdapter(plugins);
|
|
3363
|
-
}
|
|
3364
|
-
getTabLabels(counts, _t) {
|
|
3365
|
-
return {
|
|
3366
|
-
all: `[1] Alle (${counts.total})`,
|
|
3367
|
-
prod: `[2] Compile (${counts.prodCount})`,
|
|
3368
|
-
dev: `[3] Test (${counts.devCount})`
|
|
3369
|
-
};
|
|
3370
|
-
}
|
|
3371
|
-
};
|
|
3372
|
-
|
|
3373
|
-
// src/domain/ecosystem-factory.ts
|
|
3374
|
-
var EcosystemFactory = class {
|
|
3375
|
-
static instances = /* @__PURE__ */ new Map();
|
|
3376
|
-
static settings = {};
|
|
3377
|
-
/**
|
|
3378
|
-
* Stores the settings and drops the cached strategies, so strategies created
|
|
3379
|
-
* afterwards use them. Without a call the defaults apply.
|
|
3380
|
-
*/
|
|
3381
|
-
static configure(settings) {
|
|
3382
|
-
this.settings = settings;
|
|
3383
|
-
this.instances.clear();
|
|
3384
|
-
}
|
|
3385
|
-
/** Returns the strategy for an ecosystem (lazily created singleton). */
|
|
3386
|
-
static getStrategy(ecosystem) {
|
|
3387
|
-
let strategy = this.instances.get(ecosystem);
|
|
3388
|
-
if (!strategy) {
|
|
3389
|
-
if (ecosystem === "maven") {
|
|
3390
|
-
strategy = new MavenEcosystemStrategy(this.settings.mavenPlugins);
|
|
3391
|
-
} else if (ecosystem === "cargo") {
|
|
3392
|
-
strategy = new CargoEcosystemStrategy();
|
|
3393
|
-
} else {
|
|
3394
|
-
strategy = new NpmEcosystemStrategy();
|
|
3395
|
-
}
|
|
3396
|
-
this.instances.set(ecosystem, strategy);
|
|
3397
|
-
}
|
|
3398
|
-
return strategy;
|
|
3399
|
-
}
|
|
3400
|
-
/** All supported ecosystems. */
|
|
3401
|
-
static getAvailableEcosystems() {
|
|
3402
|
-
return ["npm", "maven", "cargo"];
|
|
3403
|
-
}
|
|
3404
|
-
/** The ecosystem after `current`, for the dashboard's ecosystem switch. */
|
|
3405
|
-
static next(current) {
|
|
3406
|
-
const all = this.getAvailableEcosystems();
|
|
3407
|
-
return all[(all.indexOf(current) + 1) % all.length];
|
|
3408
|
-
}
|
|
3409
|
-
};
|
|
3410
|
-
|
|
3411
|
-
// src/config.ts
|
|
3412
|
-
function findRepositoryRoot(startDir) {
|
|
3413
|
-
const start = resolve14(startDir);
|
|
3414
|
-
const boundary = findCheckoutBoundary(start);
|
|
3415
|
-
let dir = start;
|
|
3416
|
-
for (; ; ) {
|
|
3417
|
-
if (existsSync16(join23(dir, "elevate.config.json"))) return dir;
|
|
3418
|
-
const parent = dirname10(dir);
|
|
3419
|
-
if (dir === boundary || parent === dir) break;
|
|
3420
|
-
dir = parent;
|
|
3421
|
-
}
|
|
3422
|
-
const proposals = EcosystemFactory.getAvailableEcosystems().map((ecosystem) => EcosystemFactory.getStrategy(ecosystem).discovery.findRoot?.(start, boundary)).filter((root) => root !== void 0);
|
|
3423
|
-
return proposals.sort((a, b) => a.length - b.length)[0] ?? start;
|
|
3424
|
-
}
|
|
3425
|
-
var ConfigError = class extends Error {
|
|
3426
|
-
constructor(message2) {
|
|
3427
|
-
super(message2);
|
|
3428
|
-
this.name = "ConfigError";
|
|
3429
|
-
}
|
|
3430
|
-
};
|
|
3431
|
-
var NO_CONTINUE = "Elevate does not continue without your internalScopes.";
|
|
3432
|
-
function stringList(file, key, source) {
|
|
3433
|
-
if (!Object.hasOwn(file, key)) return void 0;
|
|
3434
|
-
const value = file[key];
|
|
3435
|
-
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) return value;
|
|
3436
|
-
throw new ConfigError(
|
|
3437
|
-
`Invalid elevate.config.json (${source}): \`${key}\` must be a list of strings, for example ["@my-org", "com.mycompany"]. ${NO_CONTINUE}`
|
|
3438
|
-
);
|
|
3439
|
-
}
|
|
3440
|
-
var PLUGIN_VERSION = /^[0-9][A-Za-z0-9.-]{0,63}$/;
|
|
3441
|
-
function resolveMavenPlugins(value, warnings) {
|
|
3442
|
-
const plugins = { ...DEFAULT_MAVEN_PLUGINS };
|
|
3443
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return plugins;
|
|
3444
|
-
const given = value;
|
|
3445
|
-
for (const key of Object.keys(DEFAULT_MAVEN_PLUGINS)) {
|
|
3446
|
-
if (!Object.hasOwn(given, key)) continue;
|
|
3447
|
-
const version = given[key];
|
|
3448
|
-
if (typeof version === "string" && PLUGIN_VERSION.test(version)) {
|
|
3449
|
-
plugins[key] = version;
|
|
3450
|
-
} else {
|
|
3451
|
-
warnings.push(
|
|
3452
|
-
`\`mavenPlugins.${key}\` is not a valid version (${JSON.stringify(version)}); using the default ${DEFAULT_MAVEN_PLUGINS[key]}.`
|
|
3453
|
-
);
|
|
3454
|
-
}
|
|
3455
|
-
}
|
|
3456
|
-
return plugins;
|
|
3457
|
-
}
|
|
3458
|
-
function resolveConfig(rootDir, file, source = "elevate.config.json") {
|
|
3459
|
-
const deprecations = [];
|
|
3460
|
-
const warnings = [];
|
|
3461
|
-
const internal = stringList(file, "internalScopes", source);
|
|
3462
|
-
const legacy = stringList(file, "excludeScopes", source);
|
|
3463
|
-
if (legacy) {
|
|
3464
|
-
deprecations.push(
|
|
3465
|
-
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."
|
|
3466
|
-
);
|
|
3467
|
-
}
|
|
3468
|
-
return {
|
|
3469
|
-
rootDir,
|
|
3470
|
-
author: typeof file.author === "string" ? file.author : void 0,
|
|
3471
|
-
internalScopes: internal ?? legacy ?? [],
|
|
3472
|
-
postUpdateScript: typeof file.postUpdateScript === "string" ? file.postUpdateScript : void 0,
|
|
3473
|
-
postUpdateLabel: typeof file.postUpdateLabel === "string" ? file.postUpdateLabel : typeof file.postUpdateScript === "string" ? file.postUpdateScript : void 0,
|
|
3474
|
-
locale: file.locale === "de" || file.locale === "en" ? file.locale : void 0,
|
|
3475
|
-
channel: file.channel === "all" ? "all" : "stable",
|
|
3476
|
-
deprecations,
|
|
3477
|
-
mavenPlugins: resolveMavenPlugins(file.mavenPlugins, warnings),
|
|
3478
|
-
warnings
|
|
3479
|
-
};
|
|
3480
|
-
}
|
|
3481
|
-
function loadConfig2(startDir = process.cwd()) {
|
|
3482
|
-
const rootDir = findRepositoryRoot(startDir);
|
|
3483
|
-
const configPath = join23(rootDir, "elevate.config.json");
|
|
3484
|
-
let file = {};
|
|
3485
|
-
if (existsSync16(configPath)) {
|
|
3486
|
-
const unreadable = (detail) => new ConfigError(
|
|
3487
|
-
`Cannot read elevate.config.json: ${detail} (${configPath}). Fix the file or remove it; ${NO_CONTINUE}`
|
|
3488
|
-
);
|
|
3489
|
-
let parsed;
|
|
3490
|
-
try {
|
|
3491
|
-
parsed = JSON.parse(readFileSync16(configPath, "utf8").replace(/^/, ""));
|
|
3492
|
-
} catch (err) {
|
|
3493
|
-
throw unreadable(err instanceof Error ? err.message : String(err));
|
|
3494
|
-
}
|
|
3495
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3496
|
-
throw unreadable("the file must contain a JSON object");
|
|
3497
|
-
}
|
|
3498
|
-
file = parsed;
|
|
3499
|
-
}
|
|
3500
|
-
return resolveConfig(rootDir, file, configPath);
|
|
3501
|
-
}
|
|
3502
|
-
|
|
3503
18
|
// src/app.tsx
|
|
3504
19
|
import { useState as useState8, useMemo as useMemo4, useCallback as useCallback5 } from "react";
|
|
3505
20
|
import { Box as Box14, useInput as useInput2, useApp } from "ink";
|
|
@@ -3523,7 +38,9 @@ var de = {
|
|
|
3523
38
|
tabs: {
|
|
3524
39
|
all: (count) => `[1] Alle (${count})`,
|
|
3525
40
|
dependencies: (count) => `[2] Dependencies (${count})`,
|
|
3526
|
-
devDependencies: (count) => `[3] DevDependencies (${count})
|
|
41
|
+
devDependencies: (count) => `[3] DevDependencies (${count})`,
|
|
42
|
+
main: (count) => `[2] Haupt (${count})`,
|
|
43
|
+
testAndPlugins: (count) => `[3] Test & Plugins (${count})`
|
|
3527
44
|
},
|
|
3528
45
|
list: {
|
|
3529
46
|
scanning: "\u23F3 Pr\xFCfe Registry auf verf\xFCgbare Updates\u2026",
|
|
@@ -3539,7 +56,8 @@ var de = {
|
|
|
3539
56
|
"declaration-mismatch": "Ort der Versionsangabe ist nicht eindeutig",
|
|
3540
57
|
"invalid-name": "ung\xFCltiger Paketname",
|
|
3541
58
|
"not-linked": "Workspace-Paket, das pnpm ohne das workspace:-Protokoll nicht verlinkt",
|
|
3542
|
-
"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"
|
|
3543
61
|
})[reason]
|
|
3544
62
|
},
|
|
3545
63
|
badges: {
|
|
@@ -3635,7 +153,9 @@ var en = {
|
|
|
3635
153
|
tabs: {
|
|
3636
154
|
all: (count) => `[1] All (${count})`,
|
|
3637
155
|
dependencies: (count) => `[2] Dependencies (${count})`,
|
|
3638
|
-
devDependencies: (count) => `[3] DevDependencies (${count})
|
|
156
|
+
devDependencies: (count) => `[3] DevDependencies (${count})`,
|
|
157
|
+
main: (count) => `[2] Main (${count})`,
|
|
158
|
+
testAndPlugins: (count) => `[3] Test & plugins (${count})`
|
|
3639
159
|
},
|
|
3640
160
|
list: {
|
|
3641
161
|
scanning: "\u23F3 Checking registry for available updates\u2026",
|
|
@@ -3651,7 +171,8 @@ var en = {
|
|
|
3651
171
|
"declaration-mismatch": "version location is ambiguous",
|
|
3652
172
|
"invalid-name": "invalid package name",
|
|
3653
173
|
"not-linked": "workspace package that pnpm does not link without the workspace: protocol",
|
|
3654
|
-
"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"
|
|
3655
176
|
})[reason]
|
|
3656
177
|
},
|
|
3657
178
|
badges: {
|
|
@@ -3800,6 +321,15 @@ async function scanModules(strategy, modules, context) {
|
|
|
3800
321
|
for (const module of modules) results.set(module, await scanModule(strategy, module, context));
|
|
3801
322
|
return results;
|
|
3802
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
|
+
}
|
|
3803
333
|
function candidateToJson(u) {
|
|
3804
334
|
return {
|
|
3805
335
|
identifier: u.coordinate.identifier,
|
|
@@ -3813,12 +343,8 @@ function candidateToJson(u) {
|
|
|
3813
343
|
scope: u.scope,
|
|
3814
344
|
isPreRelease: u.isPreRelease ?? false,
|
|
3815
345
|
preReleaseTag: u.preReleaseTag,
|
|
3816
|
-
declaredIn: u.declaration ?
|
|
3817
|
-
|
|
3818
|
-
kind: u.declaration.kind,
|
|
3819
|
-
property: u.declaration.propertyName,
|
|
3820
|
-
catalog: u.declaration.catalogName
|
|
3821
|
-
} : 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,
|
|
3822
348
|
sharedWith: u.sharedWith
|
|
3823
349
|
};
|
|
3824
350
|
}
|
|
@@ -3982,24 +508,24 @@ function usePackages(strategy, module, modules, config, channel = "stable") {
|
|
|
3982
508
|
import { useState as useState4, useCallback as useCallback4 } from "react";
|
|
3983
509
|
|
|
3984
510
|
// src/application/update-workflow.ts
|
|
3985
|
-
import { existsSync as
|
|
3986
|
-
import { relative
|
|
511
|
+
import { existsSync as existsSync2 } from "fs";
|
|
512
|
+
import { relative } from "path";
|
|
3987
513
|
|
|
3988
514
|
// src/adapters/shared/file-snapshot.ts
|
|
3989
|
-
import { existsSync
|
|
515
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
3990
516
|
var FileSnapshot = class _FileSnapshot {
|
|
3991
|
-
constructor(
|
|
3992
|
-
this.entries =
|
|
517
|
+
constructor(entries) {
|
|
518
|
+
this.entries = entries;
|
|
3993
519
|
}
|
|
3994
520
|
entries;
|
|
3995
521
|
/** Records the current content (or absence) of each file. */
|
|
3996
522
|
static capture(files) {
|
|
3997
|
-
const
|
|
523
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3998
524
|
for (const file of files) {
|
|
3999
|
-
if (
|
|
4000
|
-
|
|
525
|
+
if (entries.has(file)) continue;
|
|
526
|
+
entries.set(file, existsSync(file) ? readFileSync(file) : null);
|
|
4001
527
|
}
|
|
4002
|
-
return new _FileSnapshot(
|
|
528
|
+
return new _FileSnapshot(entries);
|
|
4003
529
|
}
|
|
4004
530
|
/** The files covered by this snapshot. */
|
|
4005
531
|
get files() {
|
|
@@ -4009,18 +535,18 @@ var FileSnapshot = class _FileSnapshot {
|
|
|
4009
535
|
changedFiles() {
|
|
4010
536
|
return this.files.filter((file) => {
|
|
4011
537
|
const before = this.entries.get(file) ?? null;
|
|
4012
|
-
const exists =
|
|
538
|
+
const exists = existsSync(file);
|
|
4013
539
|
if (before === null) return exists;
|
|
4014
|
-
return !exists || !
|
|
540
|
+
return !exists || !readFileSync(file).equals(before);
|
|
4015
541
|
});
|
|
4016
542
|
}
|
|
4017
543
|
/** Writes every file back to its captured state; removes files that did not exist. */
|
|
4018
544
|
restore() {
|
|
4019
545
|
for (const [file, content] of this.entries) {
|
|
4020
546
|
if (content === null) {
|
|
4021
|
-
|
|
547
|
+
rmSync(file, { force: true });
|
|
4022
548
|
} else {
|
|
4023
|
-
|
|
549
|
+
writeFileSync(file, content);
|
|
4024
550
|
}
|
|
4025
551
|
}
|
|
4026
552
|
}
|
|
@@ -4059,7 +585,7 @@ Restoring the installed state failed as well: ${message(err)}`;
|
|
|
4059
585
|
let baseline;
|
|
4060
586
|
if (!options.skipVerification) {
|
|
4061
587
|
progress("Running verification on the current state\u2026");
|
|
4062
|
-
baseline = await verify(snapshot.files.filter((file) =>
|
|
588
|
+
baseline = await verify(snapshot.files.filter((file) => existsSync2(file)));
|
|
4063
589
|
}
|
|
4064
590
|
let applied;
|
|
4065
591
|
try {
|
|
@@ -4105,7 +631,7 @@ function message(err) {
|
|
|
4105
631
|
return err instanceof Error ? err.message : String(err);
|
|
4106
632
|
}
|
|
4107
633
|
function displayPaths(files, rootDir) {
|
|
4108
|
-
return files.map((file) =>
|
|
634
|
+
return files.map((file) => relative(rootDir, file).replace(/\\/g, "/"));
|
|
4109
635
|
}
|
|
4110
636
|
|
|
4111
637
|
// src/hooks/use-updater.ts
|
|
@@ -4586,7 +1112,7 @@ var ChickenMascot = ({ state = "idle", t, ecosystem = "npm" }) => {
|
|
|
4586
1112
|
chickFace = cur.face;
|
|
4587
1113
|
wingLeft = cur.l;
|
|
4588
1114
|
wingRight = cur.r;
|
|
4589
|
-
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;
|
|
4590
1116
|
currentChirp = chirpsPool[chirpIndex % chirpsPool.length];
|
|
4591
1117
|
}
|
|
4592
1118
|
return /* @__PURE__ */ jsxs7(Box7, { alignItems: "center", children: [
|
|
@@ -4746,7 +1272,7 @@ var PublicLookupRefusedError = class extends Error {
|
|
|
4746
1272
|
}
|
|
4747
1273
|
};
|
|
4748
1274
|
function coordinateFromIdentifier(identifier, ecosystem) {
|
|
4749
|
-
if (ecosystem === "maven") {
|
|
1275
|
+
if (ecosystem === "maven" || ecosystem === "gradle") {
|
|
4750
1276
|
const [group, artifact] = identifier.split(":");
|
|
4751
1277
|
return { identifier, group, artifact: artifact || group, ecosystem };
|
|
4752
1278
|
}
|
|
@@ -5318,7 +1844,7 @@ SUBCOMMANDS:
|
|
|
5318
1844
|
mcp Start stdio-based MCP server
|
|
5319
1845
|
|
|
5320
1846
|
FLAGS:
|
|
5321
|
-
-e, --ecosystem <
|
|
1847
|
+
-e, --ecosystem <name> Target ecosystem: npm, maven, cargo or gradle (default: npm)
|
|
5322
1848
|
-m, --module <name|path> Target module ID or relative path (default: root)
|
|
5323
1849
|
-c, --channel <stable|all> Release channel (default: stable)
|
|
5324
1850
|
-p, --packages <pkg1,pkg2@1.2.3> Comma-separated list of packages to update
|
|
@@ -5334,6 +1860,7 @@ FLAGS:
|
|
|
5334
1860
|
EXAMPLES:
|
|
5335
1861
|
elevate scan --json
|
|
5336
1862
|
elevate scan --ecosystem=maven --channel=stable --all-modules --json
|
|
1863
|
+
elevate scan --ecosystem=gradle --json
|
|
5337
1864
|
elevate versions chalk --ecosystem=npm --json
|
|
5338
1865
|
elevate update --module=apps/e2e-cockpit --packages=chalk@5.6.2 --json
|
|
5339
1866
|
elevate update --ecosystem=npm --all --allow-major --dry-run
|
|
@@ -5385,6 +1912,8 @@ function describeSkip(skipped) {
|
|
|
5385
1912
|
return `catalog entry could not be resolved${detail}`;
|
|
5386
1913
|
case "incompatible-rust-version":
|
|
5387
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}`;
|
|
5388
1917
|
case "locked-version-yanked":
|
|
5389
1918
|
return `Cargo.lock pins a yanked release and no allowed release can replace it${detail}`;
|
|
5390
1919
|
}
|
|
@@ -5471,13 +2000,14 @@ async function handleScanCommand(options, config) {
|
|
|
5471
2000
|
`);
|
|
5472
2001
|
continue;
|
|
5473
2002
|
}
|
|
5474
|
-
const
|
|
2003
|
+
const prefix = module.isRoot ? "" : `${module.relPath}/`;
|
|
2004
|
+
const ownFiles = module.ecosystem === "gradle" ? [`${prefix}build.gradle`, `${prefix}build.gradle.kts`] : [`${prefix}pom.xml`];
|
|
5475
2005
|
if (result.candidates.length === 0) console.log(" \u2728 All dependencies are up to date.");
|
|
5476
2006
|
for (const u of result.candidates) {
|
|
5477
2007
|
const diffTag = `[${u.diff.toUpperCase()}]`.padEnd(9);
|
|
5478
2008
|
const actionTag = u.action === "align" ? "[ALIGN] " : u.action === "lock" ? "[LOCK] " : u.origin.kind === "private" ? "[INTERNAL] " : "";
|
|
5479
2009
|
const preBadge = u.isPreRelease ? `[${u.preReleaseTag || "PRE"}] ` : "";
|
|
5480
|
-
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}` : ""})` : "";
|
|
5481
2011
|
console.log(
|
|
5482
2012
|
` \u2022 ${diffTag} ${actionTag}${preBadge}${u.coordinate.identifier.padEnd(36)} ${u.currentRange.padEnd(12)} \u2794 ${u.latest}${where}`
|
|
5483
2013
|
);
|
|
@@ -5546,7 +2076,7 @@ async function handleVersionsCommand(options, config) {
|
|
|
5546
2076
|
}
|
|
5547
2077
|
|
|
5548
2078
|
// src/application/update-selection.ts
|
|
5549
|
-
import
|
|
2079
|
+
import semver from "semver";
|
|
5550
2080
|
var UpdateSelectionError = class extends Error {
|
|
5551
2081
|
constructor(message2) {
|
|
5552
2082
|
super(message2);
|
|
@@ -5623,7 +2153,7 @@ function retarget(found, target, options) {
|
|
|
5623
2153
|
};
|
|
5624
2154
|
}
|
|
5625
2155
|
function manualNpmCandidate(identifier, target, options) {
|
|
5626
|
-
if (!
|
|
2156
|
+
if (!semver.valid(target)) {
|
|
5627
2157
|
throw new UpdateSelectionError(`'${target}' is not a valid version for '${identifier}'.`);
|
|
5628
2158
|
}
|
|
5629
2159
|
return {
|
|
@@ -5795,9 +2325,9 @@ function parsePackageArgument(item) {
|
|
|
5795
2325
|
}
|
|
5796
2326
|
|
|
5797
2327
|
// src/cli/command-init.ts
|
|
5798
|
-
import { existsSync as
|
|
5799
|
-
import { homedir
|
|
5800
|
-
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";
|
|
5801
2331
|
function mcpServerEntry() {
|
|
5802
2332
|
return {
|
|
5803
2333
|
command: "npx",
|
|
@@ -5810,7 +2340,7 @@ var ELEVATE_SKILL_CONTENT = `# Elevate \u2014 Dependency Management Agent Skill
|
|
|
5810
2340
|
Use the Elevate MCP tools whenever the task involves:
|
|
5811
2341
|
- Scanning or auditing dependencies for available updates
|
|
5812
2342
|
- Updating one or more packages to specific or latest versions
|
|
5813
|
-
- 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
|
|
5814
2344
|
- Discovering monorepo modules and their dependency structure
|
|
5815
2345
|
- Any dependency-related operation in a monorepo
|
|
5816
2346
|
|
|
@@ -5840,10 +2370,10 @@ automatic rollback.
|
|
|
5840
2370
|
requested). A failed install or verification is rolled back automatically.
|
|
5841
2371
|
4. \`elevate_verify\` \u2014 Verify build integrity when needed
|
|
5842
2372
|
`;
|
|
5843
|
-
var home =
|
|
2373
|
+
var home = homedir();
|
|
5844
2374
|
var isWin = platform() === "win32";
|
|
5845
2375
|
function appData() {
|
|
5846
|
-
return process.env.APPDATA ||
|
|
2376
|
+
return process.env.APPDATA || join(home, "AppData", "Roaming");
|
|
5847
2377
|
}
|
|
5848
2378
|
function detectCursor(cwd) {
|
|
5849
2379
|
const env = {
|
|
@@ -5855,25 +2385,25 @@ function detectCursor(cwd) {
|
|
|
5855
2385
|
mcpConfigured: false,
|
|
5856
2386
|
skillConfigured: false
|
|
5857
2387
|
};
|
|
5858
|
-
const workspaceCursorDir =
|
|
5859
|
-
const globalCursorDir =
|
|
5860
|
-
if (
|
|
2388
|
+
const workspaceCursorDir = join(cwd, ".cursor");
|
|
2389
|
+
const globalCursorDir = join(home, ".cursor");
|
|
2390
|
+
if (existsSync3(workspaceCursorDir) || existsSync3(globalCursorDir)) {
|
|
5861
2391
|
env.detected = true;
|
|
5862
|
-
env.reason =
|
|
2392
|
+
env.reason = existsSync3(workspaceCursorDir) ? `Found .cursor/ in workspace` : `Found ~/.cursor/ global config`;
|
|
5863
2393
|
}
|
|
5864
|
-
const mcpPath =
|
|
5865
|
-
if (
|
|
2394
|
+
const mcpPath = join(cwd, ".cursor", "mcp.json");
|
|
2395
|
+
if (existsSync3(mcpPath)) {
|
|
5866
2396
|
try {
|
|
5867
|
-
const content = JSON.parse(
|
|
2397
|
+
const content = JSON.parse(readFileSync2(mcpPath, "utf8"));
|
|
5868
2398
|
if (content?.mcpServers?.elevate) {
|
|
5869
2399
|
env.mcpConfigured = true;
|
|
5870
2400
|
}
|
|
5871
2401
|
} catch {
|
|
5872
2402
|
}
|
|
5873
2403
|
}
|
|
5874
|
-
const rulesPath =
|
|
5875
|
-
if (
|
|
5876
|
-
const content =
|
|
2404
|
+
const rulesPath = join(cwd, ".cursorrules");
|
|
2405
|
+
if (existsSync3(rulesPath)) {
|
|
2406
|
+
const content = readFileSync2(rulesPath, "utf8");
|
|
5877
2407
|
if (content.includes("elevate") || content.includes("Elevate")) {
|
|
5878
2408
|
env.skillConfigured = true;
|
|
5879
2409
|
}
|
|
@@ -5890,12 +2420,12 @@ function detectClaudeDesktop() {
|
|
|
5890
2420
|
mcpConfigured: false,
|
|
5891
2421
|
skillConfigured: false
|
|
5892
2422
|
};
|
|
5893
|
-
const configPath = isWin ?
|
|
5894
|
-
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)) {
|
|
5895
2425
|
env.detected = true;
|
|
5896
2426
|
env.reason = `Found ${configPath}`;
|
|
5897
2427
|
try {
|
|
5898
|
-
const content = JSON.parse(
|
|
2428
|
+
const content = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
5899
2429
|
if (content?.mcpServers?.elevate) {
|
|
5900
2430
|
env.mcpConfigured = true;
|
|
5901
2431
|
}
|
|
@@ -5914,23 +2444,23 @@ function detectClaudeCode(cwd) {
|
|
|
5914
2444
|
mcpConfigured: false,
|
|
5915
2445
|
skillConfigured: false
|
|
5916
2446
|
};
|
|
5917
|
-
const mcpJsonPath =
|
|
5918
|
-
const claudeMdPath =
|
|
5919
|
-
if (
|
|
2447
|
+
const mcpJsonPath = join(cwd, ".mcp.json");
|
|
2448
|
+
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
2449
|
+
if (existsSync3(mcpJsonPath) || existsSync3(claudeMdPath)) {
|
|
5920
2450
|
env.detected = true;
|
|
5921
|
-
env.reason =
|
|
2451
|
+
env.reason = existsSync3(mcpJsonPath) ? `Found .mcp.json in workspace` : `Found CLAUDE.md in workspace`;
|
|
5922
2452
|
}
|
|
5923
|
-
if (
|
|
2453
|
+
if (existsSync3(mcpJsonPath)) {
|
|
5924
2454
|
try {
|
|
5925
|
-
const content = JSON.parse(
|
|
2455
|
+
const content = JSON.parse(readFileSync2(mcpJsonPath, "utf8"));
|
|
5926
2456
|
if (content?.mcpServers?.elevate) {
|
|
5927
2457
|
env.mcpConfigured = true;
|
|
5928
2458
|
}
|
|
5929
2459
|
} catch {
|
|
5930
2460
|
}
|
|
5931
2461
|
}
|
|
5932
|
-
if (
|
|
5933
|
-
const content =
|
|
2462
|
+
if (existsSync3(claudeMdPath)) {
|
|
2463
|
+
const content = readFileSync2(claudeMdPath, "utf8");
|
|
5934
2464
|
if (content.includes("elevate") || content.includes("Elevate")) {
|
|
5935
2465
|
env.skillConfigured = true;
|
|
5936
2466
|
}
|
|
@@ -5947,20 +2477,20 @@ function detectAntigravity(cwd) {
|
|
|
5947
2477
|
mcpConfigured: false,
|
|
5948
2478
|
skillConfigured: false
|
|
5949
2479
|
};
|
|
5950
|
-
const globalGeminiDir =
|
|
5951
|
-
const workspaceAgentsDir =
|
|
5952
|
-
if (
|
|
2480
|
+
const globalGeminiDir = join(home, ".gemini");
|
|
2481
|
+
const workspaceAgentsDir = join(cwd, ".agents");
|
|
2482
|
+
if (existsSync3(globalGeminiDir) || existsSync3(workspaceAgentsDir)) {
|
|
5953
2483
|
env.detected = true;
|
|
5954
|
-
env.reason =
|
|
2484
|
+
env.reason = existsSync3(workspaceAgentsDir) ? `Found .agents/ in workspace` : `Found ~/.gemini/ global config`;
|
|
5955
2485
|
}
|
|
5956
2486
|
const mcpPaths = [
|
|
5957
|
-
|
|
5958
|
-
|
|
2487
|
+
join(cwd, ".agents", "mcp_config.json"),
|
|
2488
|
+
join(home, ".gemini", "config", "mcp_config.json")
|
|
5959
2489
|
];
|
|
5960
2490
|
for (const p of mcpPaths) {
|
|
5961
|
-
if (
|
|
2491
|
+
if (existsSync3(p)) {
|
|
5962
2492
|
try {
|
|
5963
|
-
const content = JSON.parse(
|
|
2493
|
+
const content = JSON.parse(readFileSync2(p, "utf8"));
|
|
5964
2494
|
if (content?.mcpServers?.elevate) {
|
|
5965
2495
|
env.mcpConfigured = true;
|
|
5966
2496
|
break;
|
|
@@ -5981,24 +2511,24 @@ function detectVSCode(cwd) {
|
|
|
5981
2511
|
mcpConfigured: false,
|
|
5982
2512
|
skillConfigured: false
|
|
5983
2513
|
};
|
|
5984
|
-
const vscodeDir =
|
|
5985
|
-
if (
|
|
2514
|
+
const vscodeDir = join(cwd, ".vscode");
|
|
2515
|
+
if (existsSync3(vscodeDir)) {
|
|
5986
2516
|
env.detected = true;
|
|
5987
2517
|
env.reason = `Found .vscode/ in workspace`;
|
|
5988
2518
|
}
|
|
5989
|
-
const mcpPath =
|
|
5990
|
-
if (
|
|
2519
|
+
const mcpPath = join(cwd, ".vscode", "mcp.json");
|
|
2520
|
+
if (existsSync3(mcpPath)) {
|
|
5991
2521
|
try {
|
|
5992
|
-
const content = JSON.parse(
|
|
2522
|
+
const content = JSON.parse(readFileSync2(mcpPath, "utf8"));
|
|
5993
2523
|
if (content?.servers?.elevate) {
|
|
5994
2524
|
env.mcpConfigured = true;
|
|
5995
2525
|
}
|
|
5996
2526
|
} catch {
|
|
5997
2527
|
}
|
|
5998
2528
|
}
|
|
5999
|
-
const copilotInstructions =
|
|
6000
|
-
if (
|
|
6001
|
-
const content =
|
|
2529
|
+
const copilotInstructions = join(cwd, ".github", "copilot-instructions.md");
|
|
2530
|
+
if (existsSync3(copilotInstructions)) {
|
|
2531
|
+
const content = readFileSync2(copilotInstructions, "utf8");
|
|
6002
2532
|
if (content.includes("elevate") || content.includes("Elevate")) {
|
|
6003
2533
|
env.skillConfigured = true;
|
|
6004
2534
|
}
|
|
@@ -6015,14 +2545,14 @@ function detectWindsurf() {
|
|
|
6015
2545
|
mcpConfigured: false,
|
|
6016
2546
|
skillConfigured: false
|
|
6017
2547
|
};
|
|
6018
|
-
const configPath = isWin ?
|
|
6019
|
-
const altPath =
|
|
6020
|
-
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)) {
|
|
6021
2551
|
env.detected = true;
|
|
6022
2552
|
env.reason = `Found Windsurf config`;
|
|
6023
|
-
const path =
|
|
2553
|
+
const path = existsSync3(configPath) ? configPath : altPath;
|
|
6024
2554
|
try {
|
|
6025
|
-
const content = JSON.parse(
|
|
2555
|
+
const content = JSON.parse(readFileSync2(path, "utf8"));
|
|
6026
2556
|
if (content?.mcpServers?.elevate) {
|
|
6027
2557
|
env.mcpConfigured = true;
|
|
6028
2558
|
}
|
|
@@ -6033,9 +2563,9 @@ function detectWindsurf() {
|
|
|
6033
2563
|
}
|
|
6034
2564
|
function mergeJsonMcpServer(filePath, serverKey, topLevelKey) {
|
|
6035
2565
|
let existing = {};
|
|
6036
|
-
if (
|
|
2566
|
+
if (existsSync3(filePath)) {
|
|
6037
2567
|
try {
|
|
6038
|
-
existing = JSON.parse(
|
|
2568
|
+
existing = JSON.parse(readFileSync2(filePath, "utf8"));
|
|
6039
2569
|
} catch {
|
|
6040
2570
|
existing = {};
|
|
6041
2571
|
}
|
|
@@ -6047,69 +2577,69 @@ function mergeJsonMcpServer(filePath, serverKey, topLevelKey) {
|
|
|
6047
2577
|
servers[serverKey] = mcpServerEntry();
|
|
6048
2578
|
existing[topLevelKey] = servers;
|
|
6049
2579
|
const dir = filePath.substring(0, filePath.lastIndexOf(isWin ? "\\" : "/"));
|
|
6050
|
-
if (dir && !
|
|
6051
|
-
|
|
2580
|
+
if (dir && !existsSync3(dir)) {
|
|
2581
|
+
mkdirSync(dir, { recursive: true });
|
|
6052
2582
|
}
|
|
6053
|
-
|
|
2583
|
+
writeFileSync2(filePath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
6054
2584
|
return { written: true, message: `${filePath}: elevate MCP server registered` };
|
|
6055
2585
|
}
|
|
6056
2586
|
function appendSkillToFile(filePath, content, marker) {
|
|
6057
|
-
if (
|
|
6058
|
-
const existing =
|
|
2587
|
+
if (existsSync3(filePath)) {
|
|
2588
|
+
const existing = readFileSync2(filePath, "utf8");
|
|
6059
2589
|
if (existing.includes(marker)) {
|
|
6060
2590
|
return { written: false, message: `${filePath}: skill already present` };
|
|
6061
2591
|
}
|
|
6062
|
-
|
|
2592
|
+
writeFileSync2(filePath, existing + "\n\n" + content, "utf8");
|
|
6063
2593
|
return { written: true, message: `${filePath}: skill appended` };
|
|
6064
2594
|
}
|
|
6065
2595
|
const dir = filePath.substring(0, filePath.lastIndexOf(isWin ? "\\" : "/"));
|
|
6066
|
-
if (dir && !
|
|
6067
|
-
|
|
2596
|
+
if (dir && !existsSync3(dir)) {
|
|
2597
|
+
mkdirSync(dir, { recursive: true });
|
|
6068
2598
|
}
|
|
6069
|
-
|
|
2599
|
+
writeFileSync2(filePath, content, "utf8");
|
|
6070
2600
|
return { written: true, message: `${filePath}: skill created` };
|
|
6071
2601
|
}
|
|
6072
2602
|
function configureCursor(cwd, result) {
|
|
6073
|
-
const mcpPath =
|
|
2603
|
+
const mcpPath = join(cwd, ".cursor", "mcp.json");
|
|
6074
2604
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
6075
2605
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Cursor MCP: ${mcp.message}`);
|
|
6076
|
-
const rulesPath =
|
|
2606
|
+
const rulesPath = join(cwd, ".cursorrules");
|
|
6077
2607
|
const skill = appendSkillToFile(rulesPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
|
|
6078
2608
|
result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Cursor Rules: ${skill.message}`);
|
|
6079
2609
|
}
|
|
6080
2610
|
function configureClaudeDesktop(result) {
|
|
6081
|
-
const configPath = isWin ?
|
|
2611
|
+
const configPath = isWin ? join(appData(), "Claude", "claude_desktop_config.json") : join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
6082
2612
|
const mcp = mergeJsonMcpServer(configPath, "elevate", "mcpServers");
|
|
6083
2613
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Claude Desktop MCP: ${mcp.message}`);
|
|
6084
2614
|
}
|
|
6085
2615
|
function configureClaudeCode(cwd, result) {
|
|
6086
|
-
const mcpPath =
|
|
2616
|
+
const mcpPath = join(cwd, ".mcp.json");
|
|
6087
2617
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
6088
2618
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Claude Code MCP: ${mcp.message}`);
|
|
6089
|
-
const claudeMdPath =
|
|
2619
|
+
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
6090
2620
|
const skill = appendSkillToFile(claudeMdPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
|
|
6091
2621
|
result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Claude Code Skill: ${skill.message}`);
|
|
6092
2622
|
}
|
|
6093
2623
|
function configureAntigravity(cwd, result) {
|
|
6094
|
-
const mcpPath =
|
|
2624
|
+
const mcpPath = join(cwd, ".agents", "mcp_config.json");
|
|
6095
2625
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
6096
2626
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Antigravity MCP: ${mcp.message}`);
|
|
6097
2627
|
}
|
|
6098
2628
|
function configureVSCode(cwd, result) {
|
|
6099
|
-
const mcpPath =
|
|
2629
|
+
const mcpPath = join(cwd, ".vscode", "mcp.json");
|
|
6100
2630
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "servers");
|
|
6101
2631
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} VS Code MCP: ${mcp.message}`);
|
|
6102
|
-
const instructionsPath =
|
|
2632
|
+
const instructionsPath = join(cwd, ".github", "copilot-instructions.md");
|
|
6103
2633
|
const skill = appendSkillToFile(instructionsPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
|
|
6104
2634
|
result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Copilot Instructions: ${skill.message}`);
|
|
6105
2635
|
}
|
|
6106
2636
|
function configureWindsurf(result) {
|
|
6107
|
-
const configPath = isWin ?
|
|
2637
|
+
const configPath = isWin ? join(appData(), "Codeium", "Windsurf", "mcp_config.json") : join(home, ".codeium", "windsurf", "mcp_config.json");
|
|
6108
2638
|
const mcp = mergeJsonMcpServer(configPath, "elevate", "mcpServers");
|
|
6109
2639
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Windsurf MCP: ${mcp.message}`);
|
|
6110
2640
|
}
|
|
6111
2641
|
async function handleInitCommand(options) {
|
|
6112
|
-
const cwd =
|
|
2642
|
+
const cwd = resolve(process.cwd());
|
|
6113
2643
|
const result = {
|
|
6114
2644
|
cwd,
|
|
6115
2645
|
environments: [],
|
|
@@ -6137,7 +2667,7 @@ async function handleInitCommand(options) {
|
|
|
6137
2667
|
console.log(` Creating portable .mcp.json for broad compatibility.
|
|
6138
2668
|
`);
|
|
6139
2669
|
}
|
|
6140
|
-
const mcpPath =
|
|
2670
|
+
const mcpPath = join(cwd, ".mcp.json");
|
|
6141
2671
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
6142
2672
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Generic MCP: ${mcp.message}`);
|
|
6143
2673
|
} else {
|
|
@@ -6224,7 +2754,7 @@ async function startMcpServer(config) {
|
|
|
6224
2754
|
tools: [
|
|
6225
2755
|
{
|
|
6226
2756
|
name: "elevate_discover_modules",
|
|
6227
|
-
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).",
|
|
6228
2758
|
inputSchema: {
|
|
6229
2759
|
type: "object",
|
|
6230
2760
|
properties: {
|
|
@@ -6264,7 +2794,7 @@ async function startMcpServer(config) {
|
|
|
6264
2794
|
ecosystem: ECOSYSTEM_PROPERTY,
|
|
6265
2795
|
identifier: {
|
|
6266
2796
|
type: "string",
|
|
6267
|
-
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)"
|
|
6268
2798
|
}
|
|
6269
2799
|
},
|
|
6270
2800
|
required: ["identifier"]
|
|
@@ -6357,7 +2887,8 @@ async function startMcpServer(config) {
|
|
|
6357
2887
|
name: m.name,
|
|
6358
2888
|
relPath: m.relPath,
|
|
6359
2889
|
isRoot: m.isRoot,
|
|
6360
|
-
version: m.version
|
|
2890
|
+
version: m.version,
|
|
2891
|
+
...m.buildKind ? { buildKind: m.buildKind } : {}
|
|
6361
2892
|
}))
|
|
6362
2893
|
});
|
|
6363
2894
|
}
|
|
@@ -6472,7 +3003,7 @@ import { jsx as jsx15 } from "react/jsx-runtime";
|
|
|
6472
3003
|
async function main() {
|
|
6473
3004
|
let config;
|
|
6474
3005
|
try {
|
|
6475
|
-
config =
|
|
3006
|
+
config = loadConfig();
|
|
6476
3007
|
} catch (err) {
|
|
6477
3008
|
if (!(err instanceof ConfigError)) throw err;
|
|
6478
3009
|
process.stderr.write(`\u274C ${err.message}
|
|
@@ -6521,7 +3052,8 @@ async function main() {
|
|
|
6521
3052
|
render(/* @__PURE__ */ jsx15(App, { config }));
|
|
6522
3053
|
}
|
|
6523
3054
|
main().catch((err) => {
|
|
6524
|
-
|
|
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}`);
|
|
6525
3057
|
process.exit(1);
|
|
6526
3058
|
});
|
|
6527
3059
|
//# sourceMappingURL=cli.js.map
|