@dennisrepa/elevate 1.3.0 → 1.5.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/LICENSE +21 -21
- package/README.md +7 -3
- package/dist/cli.js +1914 -562
- package/dist/cli.js.map +1 -1
- package/docs/de.md +43 -3
- package/docs/en.md +44 -5
- package/package.json +16 -9
package/dist/cli.js
CHANGED
|
@@ -4,74 +4,82 @@
|
|
|
4
4
|
import { render } from "ink";
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
|
-
import { existsSync as
|
|
8
|
-
import { dirname as
|
|
7
|
+
import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
|
|
8
|
+
import { dirname as dirname10, join as join23, resolve as resolve14 } from "path";
|
|
9
9
|
|
|
10
|
-
// src/
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
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);
|
|
20
18
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
if (readJson(join(dir, "package.json"))?.workspaces) return dir;
|
|
31
|
-
const parent = dirname(dir);
|
|
32
|
-
if (dir === boundary || parent === dir) return void 0;
|
|
33
|
-
dir = parent;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
async discover(rootDir) {
|
|
37
|
-
const rootPkg = readJson(join(rootDir, "package.json"));
|
|
38
|
-
const modules = [
|
|
39
|
-
{
|
|
40
|
-
id: rootPkg?.name ?? "root",
|
|
41
|
-
name: rootPkg?.name ?? "Root Workspace (npm)",
|
|
42
|
-
path: rootDir,
|
|
43
|
-
relPath: "Root",
|
|
44
|
-
ecosystem: "npm",
|
|
45
|
-
isRoot: true,
|
|
46
|
-
version: typeof rootPkg?.version === "string" ? rootPkg.version : void 0
|
|
47
|
-
}
|
|
48
|
-
];
|
|
49
|
-
if (!rootPkg?.workspaces) return modules;
|
|
50
|
-
let workspaces;
|
|
51
|
-
try {
|
|
52
|
-
workspaces = await mapWorkspaces({ cwd: rootDir, pkg: rootPkg });
|
|
53
|
-
} catch {
|
|
54
|
-
return modules;
|
|
55
|
-
}
|
|
56
|
-
for (const [name, path] of workspaces) {
|
|
57
|
-
const pkg = readJson(join(path, "package.json"));
|
|
58
|
-
modules.push({
|
|
59
|
-
id: name,
|
|
60
|
-
name,
|
|
61
|
-
path,
|
|
62
|
-
relPath: relative(rootDir, path).replace(/\\/g, "/"),
|
|
63
|
-
ecosystem: "npm",
|
|
64
|
-
isRoot: false,
|
|
65
|
-
version: typeof pkg?.version === "string" ? pkg.version : void 0
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
modules.sort((a, b) => a.isRoot ? -1 : b.isRoot ? 1 : a.relPath.localeCompare(b.relPath));
|
|
69
|
-
return modules;
|
|
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 ?? ""}`;
|
|
70
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
|
|
71
65
|
};
|
|
72
66
|
|
|
73
|
-
// src/adapters/npm/npm-
|
|
74
|
-
import
|
|
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
|
+
}
|
|
75
83
|
|
|
76
84
|
// src/adapters/shared/process.ts
|
|
77
85
|
import { exec, spawn } from "child_process";
|
|
@@ -86,7 +94,7 @@ var UnsafeArgumentError = class extends Error {
|
|
|
86
94
|
var IS_WINDOWS = process.platform === "win32";
|
|
87
95
|
var WINDOWS_UNSAFE = /["%!^&|<>\r\n]/;
|
|
88
96
|
function runCommand(command, args, options) {
|
|
89
|
-
return new Promise((
|
|
97
|
+
return new Promise((resolve16, reject) => {
|
|
90
98
|
const spawnOptions = { cwd: options.cwd, windowsHide: true, env: process.env };
|
|
91
99
|
let child2;
|
|
92
100
|
if (IS_WINDOWS) {
|
|
@@ -116,19 +124,19 @@ function runCommand(command, args, options) {
|
|
|
116
124
|
});
|
|
117
125
|
child2.on("close", (code) => {
|
|
118
126
|
if (timer) clearTimeout(timer);
|
|
119
|
-
|
|
127
|
+
resolve16({ exitCode: code ?? 1, stdout, stderr, timedOut });
|
|
120
128
|
});
|
|
121
129
|
});
|
|
122
130
|
}
|
|
123
131
|
function runShellScript(script, options) {
|
|
124
|
-
return new Promise((
|
|
132
|
+
return new Promise((resolve16) => {
|
|
125
133
|
exec(
|
|
126
134
|
script,
|
|
127
135
|
{ cwd: options.cwd, timeout: options.timeoutMs, maxBuffer: 64 * 1024 * 1024, windowsHide: true },
|
|
128
136
|
(error, stdout, stderr) => {
|
|
129
137
|
const timedOut = Boolean(error?.killed);
|
|
130
138
|
const exitCode = error ? typeof error.code === "number" ? error.code : 1 : 0;
|
|
131
|
-
|
|
139
|
+
resolve16({ exitCode, stdout, stderr, timedOut });
|
|
132
140
|
}
|
|
133
141
|
);
|
|
134
142
|
});
|
|
@@ -148,6 +156,213 @@ function quoteWindows(arg) {
|
|
|
148
156
|
return /^[\w.:\\/@+-]+$/.test(arg) ? arg : `"${arg}"`;
|
|
149
157
|
}
|
|
150
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
|
+
|
|
151
366
|
// src/domain/origin.ts
|
|
152
367
|
function buildWorkspaceIndex(modules) {
|
|
153
368
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -194,9 +409,24 @@ function npmScopeOf(name) {
|
|
|
194
409
|
return slash > 1 ? name.slice(0, slash) : void 0;
|
|
195
410
|
}
|
|
196
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
|
+
|
|
197
427
|
// src/adapters/npm/npm-config.ts
|
|
198
428
|
var DEFAULT_REGISTRY = "https://registry.npmjs.org/";
|
|
199
|
-
var PUBLIC_NPM_HOSTS = /* @__PURE__ */ new Set(["registry.npmjs.org", "registry.npmjs.com", "registry.yarnpkg.com"]);
|
|
429
|
+
var PUBLIC_NPM_HOSTS = /* @__PURE__ */ new Set(["registry.npmjs.org", "registry.npmjs.com", "registry.yarnpkg.com", "npm.jsr.io"]);
|
|
200
430
|
var NpmConfigReader = class {
|
|
201
431
|
cache = /* @__PURE__ */ new Map();
|
|
202
432
|
/** Returns the registry configuration that applies in `cwd` (cached per directory). */
|
|
@@ -233,7 +463,8 @@ function parseNpmConfig(json) {
|
|
|
233
463
|
}
|
|
234
464
|
async function loadConfig(cwd) {
|
|
235
465
|
try {
|
|
236
|
-
const
|
|
466
|
+
const { command, args } = commands.config(driverFor(cwd));
|
|
467
|
+
const result = await runCommand(command, args, { cwd, timeoutMs: 15e3 });
|
|
237
468
|
if (result.exitCode !== 0) return parseNpmConfig({});
|
|
238
469
|
return parseNpmConfig(JSON.parse(result.stdout));
|
|
239
470
|
} catch {
|
|
@@ -260,13 +491,13 @@ var NpmRegistryAdapter = class {
|
|
|
260
491
|
if (!isValidPackageName(coordinate.identifier)) return null;
|
|
261
492
|
if (channel === "stable") {
|
|
262
493
|
const latest = await this.view(coordinate.identifier, "dist-tags.latest", cwd);
|
|
263
|
-
if (typeof latest === "string" &&
|
|
494
|
+
if (typeof latest === "string" && semver3.valid(latest) && semver3.prerelease(latest) === null) {
|
|
264
495
|
return latest;
|
|
265
496
|
}
|
|
266
497
|
const releases = (await this.getAllVersions(coordinate, cwd)).filter(
|
|
267
|
-
(v) =>
|
|
498
|
+
(v) => semver3.valid(v) && semver3.prerelease(v) === null
|
|
268
499
|
);
|
|
269
|
-
return releases.length > 0 ?
|
|
500
|
+
return releases.length > 0 ? semver3.rsort(releases)[0] : null;
|
|
270
501
|
}
|
|
271
502
|
const version = await this.view(coordinate.identifier, "version", cwd);
|
|
272
503
|
return typeof version === "string" && version ? version : null;
|
|
@@ -277,10 +508,11 @@ var NpmRegistryAdapter = class {
|
|
|
277
508
|
const list = Array.isArray(versions) ? versions : versions ? [versions] : [];
|
|
278
509
|
return list.map(String).reverse();
|
|
279
510
|
}
|
|
280
|
-
/** Runs `npm view <name> <field> --json
|
|
511
|
+
/** Runs `npm view <name> <field> --json` (`pnpm view` in a pnpm repository); resolves null on any failure. */
|
|
281
512
|
async view(name, field, cwd) {
|
|
282
513
|
try {
|
|
283
|
-
const
|
|
514
|
+
const { command, args } = commands.view(driverFor(cwd), name, field);
|
|
515
|
+
const result = await runCommand(command, args, {
|
|
284
516
|
cwd,
|
|
285
517
|
timeoutMs: LOOKUP_TIMEOUT_MS
|
|
286
518
|
});
|
|
@@ -293,44 +525,9 @@ var NpmRegistryAdapter = class {
|
|
|
293
525
|
};
|
|
294
526
|
|
|
295
527
|
// src/adapters/npm/npm-scanner.ts
|
|
296
|
-
import { readFileSync as
|
|
297
|
-
import { join as
|
|
298
|
-
import
|
|
299
|
-
|
|
300
|
-
// src/domain/versions.ts
|
|
301
|
-
import semver2 from "semver";
|
|
302
|
-
var PRE_RELEASE = /(?:^|[.\-_+]|(?<=\d))(alpha|beta|rc|cr|milestone|preview|snapshot|ea|dev|pre|(?:a|b|m)(?=\d))(?=$|[.\-_+]|\d)/i;
|
|
303
|
-
function isPreReleaseVersion(version) {
|
|
304
|
-
return PRE_RELEASE.test(version);
|
|
305
|
-
}
|
|
306
|
-
function extractPreReleaseTag(version) {
|
|
307
|
-
const match = version.match(PRE_RELEASE);
|
|
308
|
-
if (!match) return void 0;
|
|
309
|
-
const token = match[1].toLowerCase();
|
|
310
|
-
if (token === "a") return "ALPHA";
|
|
311
|
-
if (token === "b") return "BETA";
|
|
312
|
-
if (token === "m") {
|
|
313
|
-
const number = version.slice((match.index ?? 0) + match[0].length).match(/^\d+/)?.[0];
|
|
314
|
-
return `M${number ?? ""}`;
|
|
315
|
-
}
|
|
316
|
-
return token.toUpperCase();
|
|
317
|
-
}
|
|
318
|
-
function cleanJavaVersion(version) {
|
|
319
|
-
const match = version.match(/^v?(\d+(?:\.\d+){0,2})/);
|
|
320
|
-
if (!match) return null;
|
|
321
|
-
return semver2.coerce(match[1])?.version ?? null;
|
|
322
|
-
}
|
|
323
|
-
function diffVersions(from, to) {
|
|
324
|
-
const kind = semver2.diff(from, to);
|
|
325
|
-
if (kind === "major" || kind === "premajor") return "major";
|
|
326
|
-
if (kind === "minor" || kind === "preminor") return "minor";
|
|
327
|
-
return "patch";
|
|
328
|
-
}
|
|
329
|
-
function rangePrefix(range) {
|
|
330
|
-
if (range.startsWith("^")) return "^";
|
|
331
|
-
if (range.startsWith("~")) return "~";
|
|
332
|
-
return "";
|
|
333
|
-
}
|
|
528
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
529
|
+
import { join as join5 } from "path";
|
|
530
|
+
import semver4 from "semver";
|
|
334
531
|
|
|
335
532
|
// src/adapters/shared/pool.ts
|
|
336
533
|
async function mapPooled(items, concurrency, fn) {
|
|
@@ -347,72 +544,12 @@ async function mapPooled(items, concurrency, fn) {
|
|
|
347
544
|
return results;
|
|
348
545
|
}
|
|
349
546
|
|
|
350
|
-
// src/adapters/npm/npm-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
357
|
-
function findCheckoutBoundary(startDir) {
|
|
358
|
-
let dir = resolve2(startDir);
|
|
359
|
-
for (; ; ) {
|
|
360
|
-
if (existsSync(join2(dir, ".git"))) return dir;
|
|
361
|
-
const parent = dirname2(dir);
|
|
362
|
-
if (parent === dir) return void 0;
|
|
363
|
-
dir = parent;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// src/adapters/npm/npm-package-manager.ts
|
|
368
|
-
var MARKER_FILES = [
|
|
369
|
-
["npm", ["package-lock.json", "npm-shrinkwrap.json"]],
|
|
370
|
-
["pnpm", ["pnpm-lock.yaml", "pnpm-workspace.yaml"]],
|
|
371
|
-
["yarn", ["yarn.lock", ".yarnrc.yml"]],
|
|
372
|
-
["bun", ["bun.lock", "bun.lockb"]]
|
|
373
|
-
];
|
|
374
|
-
var UnsupportedPackageManagerError = class extends Error {
|
|
375
|
-
constructor(detected) {
|
|
376
|
-
super(
|
|
377
|
-
`This repository is managed by ${detected.name} (${detected.evidence}). Elevate supports npm for Node.js projects; using it here would create an npm lockfile next to ${detected.name}'s. Nothing was changed.`
|
|
378
|
-
);
|
|
379
|
-
this.name = "UnsupportedPackageManagerError";
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
function detectPackageManager(dir) {
|
|
383
|
-
const boundary = findCheckoutBoundary(dir);
|
|
384
|
-
let current = resolve3(dir);
|
|
385
|
-
for (; ; ) {
|
|
386
|
-
const found = detectIn(current);
|
|
387
|
-
if (found) return found;
|
|
388
|
-
const parent = dirname3(current);
|
|
389
|
-
if (boundary === void 0 || current === boundary || parent === current) break;
|
|
390
|
-
current = parent;
|
|
391
|
-
}
|
|
392
|
-
return { name: "npm", evidence: "no other package manager detected" };
|
|
393
|
-
}
|
|
394
|
-
function detectIn(dir) {
|
|
395
|
-
const declared = readPackageManagerField(dir);
|
|
396
|
-
const name = declared?.match(/^(npm|pnpm|yarn|bun)@/)?.[1];
|
|
397
|
-
if (declared && name) return { name, evidence: `"packageManager": "${declared}" in package.json` };
|
|
398
|
-
for (const [manager, files] of MARKER_FILES) {
|
|
399
|
-
const found = files.find((file) => existsSync2(join3(dir, file)));
|
|
400
|
-
if (found) return { name: manager, evidence: found };
|
|
401
|
-
}
|
|
402
|
-
return void 0;
|
|
403
|
-
}
|
|
404
|
-
function assertNpmManaged(rootDir) {
|
|
405
|
-
const detected = detectPackageManager(rootDir);
|
|
406
|
-
if (detected.name !== "npm") throw new UnsupportedPackageManagerError(detected);
|
|
407
|
-
}
|
|
408
|
-
function readPackageManagerField(rootDir) {
|
|
409
|
-
try {
|
|
410
|
-
const field = JSON.parse(readFileSync2(join3(rootDir, "package.json"), "utf8"))?.packageManager;
|
|
411
|
-
return typeof field === "string" ? field : void 0;
|
|
412
|
-
} catch {
|
|
413
|
-
return void 0;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
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
|
+
}
|
|
416
553
|
|
|
417
554
|
// src/adapters/npm/npm-scanner.ts
|
|
418
555
|
var CONCURRENCY = 6;
|
|
@@ -425,10 +562,11 @@ var NpmDependencyAdapter = class {
|
|
|
425
562
|
registry;
|
|
426
563
|
config;
|
|
427
564
|
async scan(module, context) {
|
|
428
|
-
|
|
565
|
+
const manager = await prepareSupportedPackageManager(context.rootDir);
|
|
566
|
+
const pnpm = manager === "pnpm";
|
|
429
567
|
let pkg;
|
|
430
568
|
try {
|
|
431
|
-
pkg = JSON.parse(
|
|
569
|
+
pkg = JSON.parse(readFileSync4(join5(module.path, "package.json"), "utf8"));
|
|
432
570
|
} catch {
|
|
433
571
|
return { candidates: [], skipped: [] };
|
|
434
572
|
}
|
|
@@ -442,13 +580,20 @@ var NpmDependencyAdapter = class {
|
|
|
442
580
|
[...npmConfig.scopedRegistries].filter(([, url]) => !isPublicNpmRegistry(url))
|
|
443
581
|
)
|
|
444
582
|
};
|
|
583
|
+
const workspaceFile = pnpm ? readPnpmWorkspace(context.rootDir) : void 0;
|
|
445
584
|
const outcomes = await mapPooled(declaredDependencies(pkg), CONCURRENCY, async (dep) => {
|
|
446
585
|
const origin = classifyOrigin(dep.name, "npm", originContext);
|
|
447
586
|
if (origin.kind === "workspace") {
|
|
448
587
|
const target = context.workspace.get(dep.name);
|
|
449
|
-
|
|
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;
|
|
450
592
|
return candidate ? { candidate } : null;
|
|
451
593
|
}
|
|
594
|
+
if (pnpm && splitRangeProtocol(dep.range).protocol === "catalog") {
|
|
595
|
+
return this.catalogOutcome(dep, origin, context, module.path, workspaceFile);
|
|
596
|
+
}
|
|
452
597
|
return this.registryOutcome(dep, origin, context);
|
|
453
598
|
});
|
|
454
599
|
const candidates = [];
|
|
@@ -461,8 +606,34 @@ var NpmDependencyAdapter = class {
|
|
|
461
606
|
candidates.sort((a, b) => (DIFF_ORDER[a.diff] ?? 3) - (DIFF_ORDER[b.diff] ?? 3));
|
|
462
607
|
return { candidates, skipped };
|
|
463
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
|
+
}
|
|
464
635
|
/** Looks up a private or public dependency in its registry. */
|
|
465
|
-
async registryOutcome(dep, origin, context) {
|
|
636
|
+
async registryOutcome(dep, origin, context, extra = {}) {
|
|
466
637
|
if (!isUpgradableRange(dep.range)) return null;
|
|
467
638
|
if (!isValidPackageName(dep.name)) {
|
|
468
639
|
return { skipped: { identifier: dep.name, origin: origin.kind, reason: "invalid-name" } };
|
|
@@ -487,9 +658,9 @@ var NpmDependencyAdapter = class {
|
|
|
487
658
|
if (!latest) {
|
|
488
659
|
return { skipped: { identifier: dep.name, origin: origin.kind, reason: "lookup-failed" } };
|
|
489
660
|
}
|
|
490
|
-
const currentClean =
|
|
491
|
-
if (!currentClean || !
|
|
492
|
-
const tag =
|
|
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;
|
|
493
664
|
const diff = diffVersions(currentClean, latest);
|
|
494
665
|
return {
|
|
495
666
|
candidate: {
|
|
@@ -504,7 +675,8 @@ var NpmDependencyAdapter = class {
|
|
|
504
675
|
origin: resolvedOrigin,
|
|
505
676
|
action: "update",
|
|
506
677
|
isPreRelease: tag !== void 0,
|
|
507
|
-
preReleaseTag: tag
|
|
678
|
+
preReleaseTag: tag,
|
|
679
|
+
...extra
|
|
508
680
|
}
|
|
509
681
|
};
|
|
510
682
|
}
|
|
@@ -523,21 +695,24 @@ function declaredDependencies(pkg) {
|
|
|
523
695
|
function isUpgradableRange(range) {
|
|
524
696
|
const trimmed = range.trim();
|
|
525
697
|
if (trimmed === "" || trimmed === "*" || trimmed.toLowerCase() === "x") return false;
|
|
526
|
-
return
|
|
698
|
+
return semver4.validRange(trimmed) !== null;
|
|
527
699
|
}
|
|
528
|
-
function alignmentCandidate(dep, target, origin) {
|
|
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:" : "";
|
|
529
704
|
const local = target.version;
|
|
530
|
-
if (!local || !
|
|
531
|
-
if (
|
|
532
|
-
const currentClean =
|
|
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";
|
|
533
708
|
const diff = diffVersions(currentClean, local);
|
|
534
|
-
const tag =
|
|
709
|
+
const tag = semver4.prerelease(local) ? extractPreReleaseTag(local) ?? "PRE" : void 0;
|
|
535
710
|
return {
|
|
536
711
|
coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "npm" },
|
|
537
712
|
currentRange: dep.range,
|
|
538
713
|
currentClean,
|
|
539
714
|
latest: local,
|
|
540
|
-
newRange: `${rangePrefix(
|
|
715
|
+
newRange: `${protocol}${rangePrefix(range)}${local}`,
|
|
541
716
|
diff,
|
|
542
717
|
scope: dep.scope,
|
|
543
718
|
selected: diff !== "major",
|
|
@@ -548,10 +723,27 @@ function alignmentCandidate(dep, target, origin) {
|
|
|
548
723
|
preReleaseTag: tag
|
|
549
724
|
};
|
|
550
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
|
+
}
|
|
551
743
|
|
|
552
744
|
// src/adapters/npm/npm-updater.ts
|
|
553
|
-
import { readFileSync as
|
|
554
|
-
import { join as
|
|
745
|
+
import { readFileSync as readFileSync7, writeFileSync } from "fs";
|
|
746
|
+
import { join as join8, relative as relative2 } from "path";
|
|
555
747
|
|
|
556
748
|
// src/domain/ports.ts
|
|
557
749
|
var InstallError = class extends Error {
|
|
@@ -564,13 +756,13 @@ var InstallError = class extends Error {
|
|
|
564
756
|
};
|
|
565
757
|
|
|
566
758
|
// src/adapters/npm/npm-lockfile.ts
|
|
567
|
-
import { existsSync as existsSync3, readFileSync as
|
|
568
|
-
import { join as
|
|
759
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
760
|
+
import { join as join6 } from "path";
|
|
569
761
|
function readLockfile(rootDir) {
|
|
570
|
-
const file =
|
|
762
|
+
const file = join6(rootDir, "package-lock.json");
|
|
571
763
|
if (!existsSync3(file)) return void 0;
|
|
572
764
|
try {
|
|
573
|
-
return JSON.parse(
|
|
765
|
+
return JSON.parse(readFileSync5(file, "utf8"));
|
|
574
766
|
} catch {
|
|
575
767
|
return void 0;
|
|
576
768
|
}
|
|
@@ -591,51 +783,168 @@ function findUnlinkedWorkspaceDependencies(lockfile, moduleRelPath, names) {
|
|
|
591
783
|
return violations;
|
|
592
784
|
}
|
|
593
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
|
+
|
|
594
867
|
// src/adapters/npm/npm-updater.ts
|
|
595
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
|
+
}
|
|
596
873
|
var NpmUpdaterAdapter = class {
|
|
597
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
|
+
}
|
|
598
883
|
return [
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
884
|
+
join8(module.path, "package.json"),
|
|
885
|
+
join8(rootDir, "package.json"),
|
|
886
|
+
join8(rootDir, "package-lock.json"),
|
|
887
|
+
join8(rootDir, "npm-shrinkwrap.json")
|
|
603
888
|
];
|
|
604
889
|
}
|
|
605
890
|
async applyUpdates(module, rootDir, updates, onProgress) {
|
|
891
|
+
let manager;
|
|
606
892
|
try {
|
|
607
|
-
|
|
893
|
+
manager = await prepareSupportedPackageManager(rootDir);
|
|
608
894
|
} catch (err) {
|
|
609
|
-
if (err instanceof UnsupportedPackageManagerError
|
|
895
|
+
if (err instanceof UnsupportedPackageManagerError || err instanceof PackageManagerNotFoundError) {
|
|
896
|
+
throw new InstallError(err.message);
|
|
897
|
+
}
|
|
610
898
|
throw err;
|
|
611
899
|
}
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
+
}
|
|
620
911
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
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
|
+
}
|
|
626
919
|
}
|
|
920
|
+
writeFileSync(pkgPath, serializeLike(manifest.source, manifest.pkg), "utf8");
|
|
627
921
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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}`;
|
|
639
948
|
const audit = parseAuditSummary(combined);
|
|
640
949
|
const fundingMatch = combined.match(/(\d+ packages? (?:is|are) looking for funding)/i);
|
|
641
950
|
return {
|
|
@@ -647,14 +956,42 @@ ${install.stderr}`;
|
|
|
647
956
|
};
|
|
648
957
|
}
|
|
649
958
|
async resync(_module, rootDir, onProgress) {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
const
|
|
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 });
|
|
653
964
|
if (result.exitCode !== 0) {
|
|
654
|
-
throw new InstallError(
|
|
965
|
+
throw new InstallError(
|
|
966
|
+
`${name} install failed while restoring the previous state`,
|
|
967
|
+
name === "pnpm" ? failureDetails(name, result) : tail(result.stderr)
|
|
968
|
+
);
|
|
655
969
|
}
|
|
656
970
|
}
|
|
657
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
|
+
}
|
|
658
995
|
var VULNERABILITY_LINE = /^(?:found\s+)?(\d+)\s+(?:(?:info|low|moderate|high|critical)\s+severity\s+)?vulnerabilit(?:y|ies)\b.*$/i;
|
|
659
996
|
function parseAuditSummary(output) {
|
|
660
997
|
for (const line of output.split(/\r?\n/)) {
|
|
@@ -663,12 +1000,13 @@ function parseAuditSummary(output) {
|
|
|
663
1000
|
}
|
|
664
1001
|
return { message: "No known vulnerabilities found.", severity: "clean" };
|
|
665
1002
|
}
|
|
666
|
-
function verifyAlignedLinks(module, rootDir, updates) {
|
|
1003
|
+
function verifyAlignedLinks(manager, module, rootDir, updates) {
|
|
667
1004
|
const aligned = updates.filter((u) => u.origin.kind === "workspace").map((u) => u.coordinate.identifier);
|
|
668
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);
|
|
669
1008
|
const lockfile = readLockfile(rootDir);
|
|
670
1009
|
if (!lockfile) return ["package-lock.json is missing; workspace links cannot be verified"];
|
|
671
|
-
const moduleRelPath = relative2(rootDir, module.path).replace(/\\/g, "/");
|
|
672
1010
|
return findUnlinkedWorkspaceDependencies(lockfile, moduleRelPath, aligned);
|
|
673
1011
|
}
|
|
674
1012
|
function serializeLike(original, value) {
|
|
@@ -682,58 +1020,801 @@ function serializeLike(original, value) {
|
|
|
682
1020
|
var VERIFY_TIMEOUT_MS = 10 * 6e4;
|
|
683
1021
|
var NpmVerificationAdapter = class {
|
|
684
1022
|
async verify(_module, rootDir, options = {}) {
|
|
685
|
-
|
|
1023
|
+
let manager = "npm";
|
|
686
1024
|
if (!options.customScript) {
|
|
687
1025
|
try {
|
|
688
|
-
|
|
1026
|
+
manager = await prepareSupportedPackageManager(rootDir);
|
|
689
1027
|
} catch (err) {
|
|
690
|
-
if (err instanceof UnsupportedPackageManagerError
|
|
1028
|
+
if (err instanceof UnsupportedPackageManagerError || err instanceof PackageManagerNotFoundError) {
|
|
1029
|
+
return { status: "warn", details: err.message, label: options.customLabel || "Dependency verification" };
|
|
1030
|
+
}
|
|
691
1031
|
throw err;
|
|
692
1032
|
}
|
|
693
1033
|
}
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
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.5.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 selectVersion(versions, options) {
|
|
1467
|
+
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));
|
|
1468
|
+
const project = options.rustVersion ? rust(options.rustVersion) : void 0;
|
|
1469
|
+
let blocked;
|
|
1470
|
+
for (const candidate of candidates) {
|
|
1471
|
+
const needs = candidate.rustVersion ? rust(candidate.rustVersion) : void 0;
|
|
1472
|
+
if (project && needs && semver5.gt(needs, project)) {
|
|
1473
|
+
blocked ??= { version: candidate.version, requires: candidate.rustVersion };
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
return { version: candidate.version, ...blocked ? { blockedByMsrv: blocked } : {} };
|
|
1477
|
+
}
|
|
1478
|
+
return { version: void 0, ...blocked ? { blockedByMsrv: blocked } : {} };
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// src/adapters/cargo/cargo-registry.ts
|
|
1482
|
+
var CargoRegistryAdapter = class {
|
|
1483
|
+
constructor(deps) {
|
|
1484
|
+
this.deps = deps;
|
|
1485
|
+
}
|
|
1486
|
+
deps;
|
|
1487
|
+
async resolveEndpoint(coordinate, cwd) {
|
|
1488
|
+
const { index, isPublic } = await this.deps.endpoint(coordinate.registry, cwd);
|
|
1489
|
+
return { url: index, isPublic };
|
|
1490
|
+
}
|
|
1491
|
+
async getLatestVersion(coordinate, channel = "stable", cwd = process.cwd()) {
|
|
1492
|
+
return selectVersion(await this.load(coordinate, cwd), { channel }).version ?? null;
|
|
1493
|
+
}
|
|
1494
|
+
/** Published, not yanked, newest first. Lookup errors propagate so they are not mistaken for "no versions". */
|
|
1495
|
+
async getAllVersions(coordinate, cwd = process.cwd()) {
|
|
1496
|
+
return (await this.load(coordinate, cwd)).filter((v) => !v.yanked && semver6.valid(v.version)).map((v) => v.version).sort(semver6.rcompare);
|
|
1497
|
+
}
|
|
1498
|
+
async load(coordinate, cwd) {
|
|
1499
|
+
const { index } = await this.deps.endpoint(coordinate.registry, cwd);
|
|
1500
|
+
return this.deps.index.versions(index, coordinate.artifact);
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
|
|
1504
|
+
// src/adapters/cargo/cargo-requirement.ts
|
|
1505
|
+
import semver7 from "semver";
|
|
1506
|
+
var SIMPLE = /^(\^|~|=)?\s*(\d+(?:\.\d+){0,2})$/;
|
|
1507
|
+
function toSemverRange(requirement) {
|
|
1508
|
+
const parts = requirement.split(",").map((p) => p.trim());
|
|
1509
|
+
if (parts.some((p) => p === "")) return null;
|
|
1510
|
+
const converted = parts.map((part) => {
|
|
1511
|
+
const bare = part.match(/^\d/);
|
|
1512
|
+
return bare ? `^${part}` : part;
|
|
1513
|
+
});
|
|
1514
|
+
return semver7.validRange(converted.join(" ")) ? converted.join(" ") : null;
|
|
1515
|
+
}
|
|
1516
|
+
function satisfiesRequirement(requirement, version) {
|
|
1517
|
+
const range = toSemverRange(requirement);
|
|
1518
|
+
return range !== null && semver7.satisfies(version, range, { includePrerelease: false });
|
|
1519
|
+
}
|
|
1520
|
+
function isUpgradableRequirement(requirement) {
|
|
1521
|
+
return SIMPLE.test(requirement.trim());
|
|
1522
|
+
}
|
|
1523
|
+
function requirementFloor(requirement) {
|
|
1524
|
+
const range = toSemverRange(requirement);
|
|
1525
|
+
return range ? semver7.minVersion(range)?.version ?? null : null;
|
|
1526
|
+
}
|
|
1527
|
+
function rewriteRequirement(current, target) {
|
|
1528
|
+
const match = current.trim().match(SIMPLE);
|
|
1529
|
+
if (!match) return target;
|
|
1530
|
+
if (semver7.prerelease(target)) return `${match[1] ?? ""}${target}`;
|
|
1531
|
+
const precision = match[2].split(".").length;
|
|
1532
|
+
const [major, minor, patch] = target.split(/[-+]/)[0].split(".");
|
|
1533
|
+
const written = [major, minor, patch].slice(0, precision).join(".");
|
|
1534
|
+
return `${match[1] ?? ""}${written}`;
|
|
1535
|
+
}
|
|
1536
|
+
function cargoDiff(from, to) {
|
|
1537
|
+
const a = semver7.parse(from);
|
|
1538
|
+
const b = semver7.parse(to);
|
|
1539
|
+
if (!a || !b) return "minor";
|
|
1540
|
+
if (a.major > 0 || b.major > a.major) {
|
|
1541
|
+
if (b.major !== a.major) return "major";
|
|
1542
|
+
return b.minor !== a.minor ? "minor" : "patch";
|
|
1543
|
+
}
|
|
1544
|
+
if (a.minor > 0) {
|
|
1545
|
+
if (b.minor !== a.minor) return "major";
|
|
1546
|
+
return "minor";
|
|
1547
|
+
}
|
|
1548
|
+
return "major";
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
// src/adapters/cargo/cargo-scanner.ts
|
|
1552
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
1553
|
+
import { join as join11, relative as relative4 } from "path";
|
|
1554
|
+
import semver8 from "semver";
|
|
1555
|
+
|
|
1556
|
+
// src/adapters/cargo/cargo-toml.ts
|
|
1557
|
+
import { parseTOML as parseTOML2 } from "toml-eslint-parser";
|
|
1558
|
+
var SECTIONS2 = {
|
|
1559
|
+
dependencies: "dependencies",
|
|
1560
|
+
"dev-dependencies": "dev-dependencies",
|
|
1561
|
+
dev_dependencies: "dev-dependencies",
|
|
1562
|
+
"build-dependencies": "build-dependencies",
|
|
1563
|
+
build_dependencies: "build-dependencies"
|
|
1564
|
+
};
|
|
1565
|
+
function parseCargoManifest(source) {
|
|
1566
|
+
const root = parseTOML2(source).body[0];
|
|
1567
|
+
const leaves = [];
|
|
1568
|
+
const keyPath = (key) => key.keys.map((k) => k.type === "TOMLBare" ? k.name : k.value);
|
|
1569
|
+
const visitPair = (prefix, pair) => {
|
|
1570
|
+
const path = [...prefix, ...keyPath(pair.key)];
|
|
1571
|
+
leaves.push({ path, node: pair.value });
|
|
1572
|
+
if (pair.value.type === "TOMLInlineTable") for (const inner of pair.value.body) visitPair(path, inner);
|
|
1573
|
+
};
|
|
1574
|
+
for (const element of root.body) {
|
|
1575
|
+
if (element.type === "TOMLKeyValue") visitPair([], element);
|
|
1576
|
+
else if (element.type === "TOMLTable") {
|
|
1577
|
+
const header = keyPath(element.key);
|
|
1578
|
+
for (const pair of element.body) visitPair(header, pair);
|
|
1579
|
+
leaves.push({ path: header, node: element });
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1583
|
+
for (const { path, node } of leaves) {
|
|
1584
|
+
const located = locateSection(path);
|
|
1585
|
+
if (!located || located.rest.length === 0) continue;
|
|
1586
|
+
const [key, field] = located.rest;
|
|
1587
|
+
const id = `${located.inWorkspace}|${located.target ?? ""}|${located.section}|${key}`;
|
|
1588
|
+
let decl = byKey.get(id);
|
|
1589
|
+
if (!decl) {
|
|
1590
|
+
decl = { key, section: located.section, target: located.target, inWorkspace: located.inWorkspace, inherited: false };
|
|
1591
|
+
byKey.set(id, decl);
|
|
1592
|
+
}
|
|
1593
|
+
if (located.rest.length === 1 && node.type === "TOMLValue" && node.kind === "string") {
|
|
1594
|
+
decl.version = literalOf(node);
|
|
1595
|
+
} else if (field === "version" && located.rest.length === 2 && node.type === "TOMLValue" && node.kind === "string") {
|
|
1596
|
+
decl.version = literalOf(node);
|
|
1597
|
+
} else if (field === "workspace" && located.rest.length === 2 && node.value === true) {
|
|
1598
|
+
decl.inherited = true;
|
|
1599
|
+
} else if (field === "package" && located.rest.length === 2 && node.type === "TOMLValue") {
|
|
1600
|
+
decl.package = String(node.value);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return [...byKey.values()];
|
|
1604
|
+
}
|
|
1605
|
+
function literalOf(node) {
|
|
1606
|
+
return { value: node.value, start: node.range[0] + 1, end: node.range[1] - 1 };
|
|
1607
|
+
}
|
|
1608
|
+
function locateSection(path) {
|
|
1609
|
+
let i = 0;
|
|
1610
|
+
let target;
|
|
1611
|
+
let inWorkspace = false;
|
|
1612
|
+
if (path[0] === "workspace" && path[1] === "dependencies") {
|
|
1613
|
+
return { section: "dependencies", inWorkspace: true, target, rest: path.slice(2) };
|
|
1614
|
+
}
|
|
1615
|
+
if (path[0] === "target" && path.length >= 3) {
|
|
1616
|
+
target = path[1];
|
|
1617
|
+
i = 2;
|
|
1618
|
+
}
|
|
1619
|
+
const section = SECTIONS2[path[i] ?? ""];
|
|
1620
|
+
if (!section) return void 0;
|
|
1621
|
+
return { section, inWorkspace, target, rest: path.slice(i + 1) };
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// src/adapters/cargo/cargo-scanner.ts
|
|
1625
|
+
var CONCURRENCY2 = 6;
|
|
1626
|
+
var DIFF_ORDER2 = { major: 0, minor: 1, patch: 2 };
|
|
1627
|
+
var CargoDependencyAdapter = class {
|
|
1628
|
+
constructor(deps) {
|
|
1629
|
+
this.deps = deps;
|
|
1630
|
+
}
|
|
1631
|
+
deps;
|
|
1632
|
+
async scan(module, context) {
|
|
1633
|
+
const metadata = await this.deps.loadMetadata(module.aggregatorDir ?? module.path);
|
|
1634
|
+
const pkg = metadata.packages.find((p) => samePath(join11(p.manifest_path, ".."), module.path));
|
|
1635
|
+
if (!pkg) return { candidates: [], skipped: [] };
|
|
1636
|
+
const own = readDeclarations(pkg.manifest_path);
|
|
1637
|
+
const rootManifest = join11(metadata.workspace_root, "Cargo.toml");
|
|
1638
|
+
const root = readDeclarations(rootManifest);
|
|
1639
|
+
const groups = groupDependencies(pkg.dependencies);
|
|
1640
|
+
const outcomes = await mapPooled(groups, CONCURRENCY2, async (group) => {
|
|
1641
|
+
const primary = group.find((d) => d.kind === null) ?? group[0];
|
|
1642
|
+
const key = primary.rename ?? primary.name;
|
|
1643
|
+
const decl = findDeclaration(own, key, primary);
|
|
1644
|
+
const located = locate(decl, root, key, context.rootDir, pkg.manifest_path, rootManifest);
|
|
1645
|
+
const scope = group.every((d) => d.kind === "dev") ? "dev" : "prod";
|
|
1646
|
+
const qualifier = primary.target ?? (primary.kind === "build" ? "build" : void 0);
|
|
1647
|
+
const common = { scope, qualifier };
|
|
1648
|
+
if (primary.source === null && primary.path) {
|
|
1649
|
+
const target = [...context.workspace.values()].find((m) => samePath(m.path, primary.path));
|
|
1650
|
+
return target ? alignment(primary, target, located, common) : null;
|
|
1651
|
+
}
|
|
1652
|
+
if (!primary.source || primary.source.startsWith("git+")) return null;
|
|
1653
|
+
const outcome = await this.registryOutcome(primary, located, pkg, metadata, common, context);
|
|
1654
|
+
const other = group.find((d) => d !== primary && !sameRequirement(d.req, primary.req));
|
|
1655
|
+
if (outcome && "candidate" in outcome && other) {
|
|
1656
|
+
outcome.note = {
|
|
1657
|
+
identifier: primary.name,
|
|
1658
|
+
origin: outcome.candidate.origin.kind,
|
|
1659
|
+
reason: "declaration-mismatch",
|
|
1660
|
+
detail: `${sectionOf(other)} requires ${other.req}; it is not changed`
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
return outcome;
|
|
1664
|
+
});
|
|
1665
|
+
const candidates = [];
|
|
1666
|
+
const skipped = [];
|
|
1667
|
+
for (const outcome of outcomes) {
|
|
1668
|
+
if (!outcome) continue;
|
|
1669
|
+
if ("candidate" in outcome) {
|
|
1670
|
+
candidates.push(outcome.candidate);
|
|
1671
|
+
if (outcome.note) skipped.push(outcome.note);
|
|
1672
|
+
} else skipped.push(outcome.skipped);
|
|
1673
|
+
}
|
|
1674
|
+
candidates.sort((a, b) => (DIFF_ORDER2[a.diff] ?? 3) - (DIFF_ORDER2[b.diff] ?? 3));
|
|
1675
|
+
return { candidates, skipped };
|
|
1676
|
+
}
|
|
1677
|
+
async registryOutcome(dep, located, pkg, metadata, common, context) {
|
|
1678
|
+
const literal = located?.version.value ?? dep.req;
|
|
1679
|
+
if (!isUpgradableRequirement(literal)) return null;
|
|
1680
|
+
if (!located || !sameRequirement(located.version.value, dep.req)) {
|
|
1681
|
+
return { skipped: { identifier: dep.name, origin: "public", reason: "declaration-mismatch", detail: `${literal} in the manifest, ${dep.req} for Cargo` } };
|
|
697
1682
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
1683
|
+
let origin = { kind: "public" };
|
|
1684
|
+
if (dep.registry) origin = { kind: "private", matchedBy: "scoped-registry", registry: dep.registry };
|
|
1685
|
+
else if (matchesInternalScope(dep.name, "cargo", context.internalScopes)) origin = { kind: "private", matchedBy: "internal-scope" };
|
|
1686
|
+
const endpoint = await this.deps.endpoint(dep.registry ?? void 0, context.rootDir);
|
|
1687
|
+
if (origin.kind === "private" && endpoint.isPublic) {
|
|
1688
|
+
return { skipped: { identifier: dep.name, origin: "private", reason: "private-on-public-registry", detail: endpoint.index } };
|
|
1689
|
+
}
|
|
1690
|
+
let versions;
|
|
1691
|
+
try {
|
|
1692
|
+
versions = await this.deps.index.versions(endpoint.index, dep.name);
|
|
1693
|
+
} catch (err) {
|
|
1694
|
+
return { skipped: { identifier: dep.name, origin: origin.kind, reason: "lookup-failed", detail: err instanceof Error ? err.message : String(err) } };
|
|
1695
|
+
}
|
|
1696
|
+
const chosen = selectVersion(versions, { channel: context.channel, rustVersion: pkg.rust_version ?? void 0 });
|
|
1697
|
+
const floor = requirementFloor(literal);
|
|
1698
|
+
if (!chosen.version) {
|
|
1699
|
+
if (chosen.blockedByMsrv && floor) {
|
|
1700
|
+
const { version, requires } = chosen.blockedByMsrv;
|
|
1701
|
+
return { skipped: { identifier: dep.name, origin: origin.kind, reason: "incompatible-rust-version", detail: ` (${version} needs Rust ${requires})` } };
|
|
1702
|
+
}
|
|
1703
|
+
return { skipped: { identifier: dep.name, origin: origin.kind, reason: "lookup-failed", detail: "no usable version in the registry" } };
|
|
1704
|
+
}
|
|
1705
|
+
const latest = chosen.version;
|
|
1706
|
+
if (!floor || !semver8.gt(latest, floor)) {
|
|
1707
|
+
const held = chosen.blockedByMsrv;
|
|
1708
|
+
if (floor && held && semver8.gt(held.version, floor)) {
|
|
1709
|
+
return { skipped: { identifier: dep.name, origin: origin.kind, reason: "incompatible-rust-version", detail: ` (${held.version} needs Rust ${held.requires})` } };
|
|
1710
|
+
}
|
|
1711
|
+
return null;
|
|
1712
|
+
}
|
|
1713
|
+
const newRange = rewriteRequirement(literal, latest);
|
|
1714
|
+
if (newRange === literal) return null;
|
|
1715
|
+
const tag = semver8.prerelease(latest) ? extractPreReleaseTag(latest) ?? "PRE" : void 0;
|
|
1716
|
+
const diff = cargoDiff(floor, latest);
|
|
1717
|
+
const candidate = {
|
|
1718
|
+
coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "cargo", ...dep.registry ? { registry: dep.registry } : {} },
|
|
1719
|
+
currentRange: literal,
|
|
1720
|
+
currentClean: floor,
|
|
1721
|
+
latest,
|
|
1722
|
+
newRange,
|
|
1723
|
+
diff,
|
|
1724
|
+
scope: common.scope,
|
|
1725
|
+
selected: diff !== "major",
|
|
1726
|
+
origin,
|
|
1727
|
+
action: "update",
|
|
1728
|
+
declaration: located.declaration,
|
|
1729
|
+
isPreRelease: tag !== void 0,
|
|
1730
|
+
preReleaseTag: tag,
|
|
1731
|
+
...common.qualifier ? { qualifier: common.qualifier } : {},
|
|
1732
|
+
...located.inherited ? { sharedWith: sharersOf(metadata, pkg, dep.rename ?? dep.name) } : {}
|
|
702
1733
|
};
|
|
1734
|
+
return { candidate };
|
|
703
1735
|
}
|
|
704
1736
|
};
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1737
|
+
function alignment(dep, target, located, common) {
|
|
1738
|
+
const local = target.version;
|
|
1739
|
+
if (!located || !local || !semver8.valid(local)) return null;
|
|
1740
|
+
const literal = located.version.value;
|
|
1741
|
+
if (!isUpgradableRequirement(literal) || satisfiesRequirement(literal, local)) return null;
|
|
1742
|
+
const floor = requirementFloor(literal) ?? "0.0.0";
|
|
1743
|
+
const diff = cargoDiff(floor, local);
|
|
1744
|
+
const tag = semver8.prerelease(local) ? extractPreReleaseTag(local) ?? "PRE" : void 0;
|
|
1745
|
+
return {
|
|
1746
|
+
candidate: {
|
|
1747
|
+
coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "cargo" },
|
|
1748
|
+
currentRange: literal,
|
|
1749
|
+
currentClean: floor,
|
|
1750
|
+
latest: local,
|
|
1751
|
+
newRange: rewriteRequirement(literal, local),
|
|
1752
|
+
diff,
|
|
1753
|
+
scope: common.scope,
|
|
1754
|
+
selected: diff !== "major",
|
|
1755
|
+
origin: { kind: "workspace", moduleId: target.id, moduleRelPath: target.relPath },
|
|
1756
|
+
action: "align",
|
|
1757
|
+
declaration: located.declaration,
|
|
1758
|
+
availableVersions: [local],
|
|
1759
|
+
isPreRelease: tag !== void 0,
|
|
1760
|
+
preReleaseTag: tag,
|
|
1761
|
+
...common.qualifier ? { qualifier: common.qualifier } : {}
|
|
1762
|
+
}
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
function groupDependencies(deps) {
|
|
1766
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1767
|
+
for (const dep of deps) {
|
|
1768
|
+
const id = `${dep.rename ?? dep.name}`;
|
|
1769
|
+
groups.set(id, [...groups.get(id) ?? [], dep]);
|
|
1770
|
+
}
|
|
1771
|
+
return [...groups.values()];
|
|
1772
|
+
}
|
|
1773
|
+
function readDeclarations(manifestPath) {
|
|
1774
|
+
try {
|
|
1775
|
+
return parseCargoManifest(readFileSync10(manifestPath, "utf8"));
|
|
1776
|
+
} catch {
|
|
1777
|
+
return [];
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
var sectionOf = (dep) => dep.kind === "dev" ? "dev-dependencies" : dep.kind === "build" ? "build-dependencies" : "dependencies";
|
|
1781
|
+
function findDeclaration(decls, key, dep) {
|
|
1782
|
+
const candidates = decls.filter((d) => !d.inWorkspace && d.key === key);
|
|
1783
|
+
return candidates.find((d) => d.section === sectionOf(dep) && (d.target ?? null) === dep.target) ?? candidates[0];
|
|
1784
|
+
}
|
|
1785
|
+
function locate(decl, root, key, repoRoot, manifestPath, rootManifest) {
|
|
1786
|
+
if (!decl) return void 0;
|
|
1787
|
+
if (decl.inherited) {
|
|
1788
|
+
const shared = root.find((d) => d.inWorkspace && d.key === key);
|
|
1789
|
+
if (!shared?.version) return void 0;
|
|
718
1790
|
return {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1791
|
+
version: shared.version,
|
|
1792
|
+
inherited: true,
|
|
1793
|
+
declaration: { file: rootManifest, kind: "dependency-management", displayPath: display(repoRoot, rootManifest) }
|
|
722
1794
|
};
|
|
723
1795
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
1796
|
+
if (!decl.version) return void 0;
|
|
1797
|
+
return {
|
|
1798
|
+
version: decl.version,
|
|
1799
|
+
inherited: false,
|
|
1800
|
+
declaration: { file: manifestPath, kind: "dependency", displayPath: display(repoRoot, manifestPath) }
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
function sharersOf(metadata, self, key) {
|
|
1804
|
+
return metadata.packages.filter((p) => p !== self).filter((p) => readDeclarations(p.manifest_path).some((d) => d.key === key && d.inherited)).map((p) => p.name);
|
|
1805
|
+
}
|
|
1806
|
+
var display = (repoRoot, file) => relative4(repoRoot, file).replace(/\\/g, "/");
|
|
1807
|
+
var samePath = (a, b) => relative4(a, b) === "";
|
|
1808
|
+
function sameRequirement(literal, req) {
|
|
1809
|
+
const a = toSemverRange(literal);
|
|
1810
|
+
const b = toSemverRange(req);
|
|
1811
|
+
return a !== null && b !== null && semver8.validRange(a) === semver8.validRange(b);
|
|
1812
|
+
}
|
|
733
1813
|
|
|
734
|
-
// src/adapters/
|
|
735
|
-
import { existsSync as
|
|
736
|
-
import {
|
|
1814
|
+
// src/adapters/cargo/cargo-updater.ts
|
|
1815
|
+
import { existsSync as existsSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync2 } from "fs";
|
|
1816
|
+
import { join as join12, resolve as resolve6 } from "path";
|
|
1817
|
+
import semver9 from "semver";
|
|
737
1818
|
|
|
738
1819
|
// src/adapters/shared/xml.ts
|
|
739
1820
|
import { SaxesParser } from "saxes";
|
|
@@ -866,9 +1947,226 @@ function localName(qualified) {
|
|
|
866
1947
|
return colon >= 0 ? qualified.slice(colon + 1) : qualified;
|
|
867
1948
|
}
|
|
868
1949
|
|
|
1950
|
+
// src/adapters/shared/text-edit.ts
|
|
1951
|
+
function applyRangeEdits(source, edits) {
|
|
1952
|
+
for (const edit of edits) {
|
|
1953
|
+
const found = source.slice(edit.start, edit.end);
|
|
1954
|
+
if (found !== edit.expected) {
|
|
1955
|
+
throw new StaleEditError(`Found '${found}', expected '${edit.expected}'. The file changed since it was scanned.`);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
const ordered = [...edits].sort((a, b) => b.start - a.start);
|
|
1959
|
+
for (let i = 1; i < ordered.length; i++) {
|
|
1960
|
+
if (ordered[i].end > ordered[i - 1].start) throw new StaleEditError("Overlapping edits on the same text.");
|
|
1961
|
+
}
|
|
1962
|
+
let result = source;
|
|
1963
|
+
for (const edit of ordered) result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);
|
|
1964
|
+
return result;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
// src/adapters/cargo/cargo-lockfile.ts
|
|
1968
|
+
import { existsSync as existsSync7, readFileSync as readFileSync11 } from "fs";
|
|
1969
|
+
import { getStaticTOMLValue as getStaticTOMLValue2, parseTOML as parseTOML3 } from "toml-eslint-parser";
|
|
1970
|
+
function readLockedVersions(lockFile) {
|
|
1971
|
+
const locked = /* @__PURE__ */ new Map();
|
|
1972
|
+
if (!existsSync7(lockFile)) return locked;
|
|
1973
|
+
try {
|
|
1974
|
+
const data = getStaticTOMLValue2(parseTOML3(readFileSync11(lockFile, "utf8")));
|
|
1975
|
+
for (const pkg of data.package ?? []) locked.set(pkg.name, [...locked.get(pkg.name) ?? [], pkg.version]);
|
|
1976
|
+
} catch {
|
|
1977
|
+
}
|
|
1978
|
+
return locked;
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
// src/adapters/cargo/cargo-updater.ts
|
|
1982
|
+
var UPDATE_TIMEOUT_MS = 10 * 6e4;
|
|
1983
|
+
var workspaceDir = (module, rootDir) => module.aggregatorDir ?? module.path ?? rootDir;
|
|
1984
|
+
var CargoUpdaterAdapter = class {
|
|
1985
|
+
affectedFiles(module, rootDir, updates) {
|
|
1986
|
+
const files = new Set(updates.map((u) => resolve6(u.declaration?.file ?? join12(module.path, "Cargo.toml"))));
|
|
1987
|
+
files.add(join12(workspaceDir(module, rootDir), "Cargo.lock"));
|
|
1988
|
+
return [...files];
|
|
1989
|
+
}
|
|
1990
|
+
async applyUpdates(module, rootDir, updates, onProgress) {
|
|
1991
|
+
const dir = workspaceDir(module, rootDir);
|
|
1992
|
+
const lockFile = join12(dir, "Cargo.lock");
|
|
1993
|
+
const lockedBefore = readLockedVersions(lockFile);
|
|
1994
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
1995
|
+
for (const update of updates) {
|
|
1996
|
+
const file = resolve6(update.declaration?.file ?? join12(module.path, "Cargo.toml"));
|
|
1997
|
+
byFile.set(file, [...byFile.get(file) ?? [], update]);
|
|
1998
|
+
}
|
|
1999
|
+
const writes = [];
|
|
2000
|
+
for (const [file, fileUpdates] of byFile) {
|
|
2001
|
+
onProgress(`Preparing ${fileUpdates.length} change(s) in ${file}\u2026`);
|
|
2002
|
+
const source = readFileSync12(file, "utf8");
|
|
2003
|
+
const decls = parseCargoManifest(source);
|
|
2004
|
+
const edits = [];
|
|
2005
|
+
for (const update of fileUpdates) {
|
|
2006
|
+
const inWorkspace = update.declaration?.kind === "dependency-management";
|
|
2007
|
+
const name = update.coordinate.identifier;
|
|
2008
|
+
const found = decls.filter(
|
|
2009
|
+
(d) => d.inWorkspace === inWorkspace && (d.package ?? d.key) === name && d.version?.value === update.currentRange
|
|
2010
|
+
);
|
|
2011
|
+
if (found.length === 0) {
|
|
2012
|
+
throw new InstallError(`${file}: cannot find the version of ${name} (${update.currentRange}) any more; rescan the module.`);
|
|
2013
|
+
}
|
|
2014
|
+
for (const d of found) {
|
|
2015
|
+
edits.push({ start: d.version.start, end: d.version.end, expected: d.version.value, replacement: update.newRange });
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
try {
|
|
2019
|
+
writes.push({ file, content: applyRangeEdits(source, edits) });
|
|
2020
|
+
} catch (err) {
|
|
2021
|
+
if (err instanceof StaleEditError) throw new InstallError(`${file}: ${err.message}`);
|
|
2022
|
+
throw err;
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
onProgress("Writing Cargo.toml files\u2026");
|
|
2026
|
+
for (const write of writes) writeFileSync2(write.file, write.content, "utf8");
|
|
2027
|
+
const registryUpdates = updates.filter((u) => u.action === "update");
|
|
2028
|
+
if (existsSync8(lockFile) && registryUpdates.length > 0) {
|
|
2029
|
+
await this.refreshLock(dir, lockFile, lockedBefore, registryUpdates, onProgress);
|
|
2030
|
+
}
|
|
2031
|
+
return {
|
|
2032
|
+
updatedCount: updates.length,
|
|
2033
|
+
auditMessage: `Updated ${updates.length} requirement(s) in ${writes.length} Cargo.toml file(s)${existsSync8(lockFile) ? "; Cargo.lock refreshed" : ""}.`,
|
|
2034
|
+
auditSeverity: "clean",
|
|
2035
|
+
integrityViolations: []
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
async resync() {
|
|
2039
|
+
}
|
|
2040
|
+
async refreshLock(dir, lockFile, before, updates, onProgress) {
|
|
2041
|
+
const specs = [];
|
|
2042
|
+
for (const update of updates) {
|
|
2043
|
+
const name = update.coordinate.identifier;
|
|
2044
|
+
const versions = before.get(name) ?? [];
|
|
2045
|
+
const old = versions.find((v) => satisfiesRequirement(update.currentRange, v)) ?? versions[0];
|
|
2046
|
+
if (old) specs.push(`${name}@${old}`);
|
|
2047
|
+
}
|
|
2048
|
+
if (specs.length === 0) return;
|
|
2049
|
+
onProgress("Updating Cargo.lock\u2026");
|
|
2050
|
+
await this.cargoUpdate(dir, specs.flatMap((s) => ["-p", s]));
|
|
2051
|
+
const after = readLockedVersions(lockFile);
|
|
2052
|
+
for (const update of updates) {
|
|
2053
|
+
const name = update.coordinate.identifier;
|
|
2054
|
+
const locked = (after.get(name) ?? []).find((v) => satisfiesRequirement(update.newRange, v));
|
|
2055
|
+
if (locked && semver9.valid(update.latest) && locked !== update.latest) {
|
|
2056
|
+
onProgress(`Pinning ${name} to ${update.latest}\u2026`);
|
|
2057
|
+
await this.cargoUpdate(dir, ["-p", `${name}@${locked}`, "--precise", update.latest]);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
async cargoUpdate(dir, args) {
|
|
2062
|
+
const result = await runCommand("cargo", ["update", ...args], { cwd: dir, timeoutMs: UPDATE_TIMEOUT_MS });
|
|
2063
|
+
if (result.exitCode !== 0) {
|
|
2064
|
+
const reason = result.timedOut ? "cargo update timed out" : "cargo update failed";
|
|
2065
|
+
throw new InstallError(reason, tail(result.stderr || result.stdout));
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
};
|
|
2069
|
+
|
|
2070
|
+
// src/adapters/cargo/cargo-verifier.ts
|
|
2071
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2072
|
+
import { join as join13, relative as relative5, resolve as resolve7 } from "path";
|
|
2073
|
+
var VERIFY_TIMEOUT_MS2 = 30 * 6e4;
|
|
2074
|
+
var same = (a, b) => relative5(resolve7(a), resolve7(b)) === "";
|
|
2075
|
+
function planCheck(metadata, module, changedFiles, locked) {
|
|
2076
|
+
const args = ["check", "--all-targets", ...locked ? ["--locked"] : []];
|
|
2077
|
+
const rootManifest = join13(metadata.workspace_root, "Cargo.toml");
|
|
2078
|
+
const memberManifests = new Set(metadata.packages.map((p) => resolve7(p.manifest_path)));
|
|
2079
|
+
if (changedFiles.some((f) => same(f, rootManifest))) {
|
|
2080
|
+
const isVirtualRoot = !memberManifests.has(resolve7(rootManifest));
|
|
2081
|
+
if (isVirtualRoot || metadata.packages.length > 1) return [...args, "--workspace"];
|
|
2082
|
+
}
|
|
2083
|
+
const changed = new Set(
|
|
2084
|
+
metadata.packages.filter((p) => changedFiles.some((f) => same(f, p.manifest_path))).map((p) => p.name)
|
|
2085
|
+
);
|
|
2086
|
+
if (changed.size === 0) changed.add(module.id);
|
|
2087
|
+
for (let grew = true; grew; ) {
|
|
2088
|
+
grew = false;
|
|
2089
|
+
for (const p of metadata.packages) {
|
|
2090
|
+
if (changed.has(p.name)) continue;
|
|
2091
|
+
if (p.dependencies.some((d) => d.source === null && d.path && changed.has(d.name))) {
|
|
2092
|
+
changed.add(p.name);
|
|
2093
|
+
grew = true;
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
const ordered = metadata.packages.filter((p) => changed.has(p.name)).map((p) => p.name);
|
|
2098
|
+
return [...args, ...ordered.flatMap((name) => ["-p", name])];
|
|
2099
|
+
}
|
|
2100
|
+
var CargoVerificationAdapter = class {
|
|
2101
|
+
constructor(deps = { loadMetadata, hasLock: (dir) => existsSync9(join13(dir, "Cargo.lock")) }) {
|
|
2102
|
+
this.deps = deps;
|
|
2103
|
+
}
|
|
2104
|
+
deps;
|
|
2105
|
+
async verify(module, rootDir, options = {}) {
|
|
2106
|
+
const dir = module.aggregatorDir ?? module.path ?? rootDir;
|
|
2107
|
+
if (options.customScript) {
|
|
2108
|
+
const label2 = options.customLabel || options.customScript;
|
|
2109
|
+
const result2 = await runShellScript(options.customScript, { cwd: dir, timeoutMs: VERIFY_TIMEOUT_MS2 });
|
|
2110
|
+
return result2.exitCode === 0 ? { status: "clean", details: "Verification script succeeded.", label: label2 } : { status: "warn", details: tail(result2.stderr || result2.stdout), label: label2 };
|
|
2111
|
+
}
|
|
2112
|
+
const metadata = await this.deps.loadMetadata(dir);
|
|
2113
|
+
const args = planCheck(metadata, module, options.changedFiles ?? [], this.deps.hasLock(dir));
|
|
2114
|
+
const label = `Cargo build (cargo ${args.join(" ")})`;
|
|
2115
|
+
const result = await runCargo(args, { cwd: dir, timeoutMs: VERIFY_TIMEOUT_MS2 });
|
|
2116
|
+
if (result.exitCode === 0) return { status: "clean", details: "All affected crates type-check, including tests.", label };
|
|
2117
|
+
return {
|
|
2118
|
+
status: "warn",
|
|
2119
|
+
details: result.timedOut ? "cargo check timed out." : cargoErrors(`${result.stderr}
|
|
2120
|
+
${result.stdout}`),
|
|
2121
|
+
label
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
|
|
2126
|
+
// src/adapters/cargo/cargo-strategy.ts
|
|
2127
|
+
var cargoVersionPolicy = {
|
|
2128
|
+
normalize: (v) => semver10.valid(v),
|
|
2129
|
+
rewrite: rewriteRequirement,
|
|
2130
|
+
diff: (current, target) => semver10.valid(current) && semver10.valid(target) ? semver10.eq(current, target) ? "patch" : cargoDiff(current, target) : "minor",
|
|
2131
|
+
// Updates are written where the scan found the declaration.
|
|
2132
|
+
allowsManualTargets: false
|
|
2133
|
+
};
|
|
2134
|
+
var CargoEcosystemStrategy = class {
|
|
2135
|
+
ecosystem = "cargo";
|
|
2136
|
+
displayName = "Rust / Cargo";
|
|
2137
|
+
icon = "\u{1F980}";
|
|
2138
|
+
manifestFile = "Cargo.toml";
|
|
2139
|
+
versioning = cargoVersionPolicy;
|
|
2140
|
+
config = new CargoConfigReader();
|
|
2141
|
+
index = new CargoIndexClient();
|
|
2142
|
+
endpoint = (registry, cwd) => this.config.resolve(registry, cwd);
|
|
2143
|
+
discovery = new CargoModuleDiscoveryAdapter();
|
|
2144
|
+
registry = new CargoRegistryAdapter({ index: this.index, endpoint: this.endpoint });
|
|
2145
|
+
reader = new CargoDependencyAdapter({ loadMetadata, index: this.index, endpoint: this.endpoint });
|
|
2146
|
+
updater = new CargoUpdaterAdapter();
|
|
2147
|
+
verifier = new CargoVerificationAdapter();
|
|
2148
|
+
getTabLabels(counts, t) {
|
|
2149
|
+
return {
|
|
2150
|
+
all: t.tabs.all(counts.total),
|
|
2151
|
+
prod: t.tabs.dependencies(counts.prodCount),
|
|
2152
|
+
dev: t.tabs.devDependencies(counts.devCount)
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
|
|
2157
|
+
// src/adapters/maven/maven-discovery.ts
|
|
2158
|
+
import { existsSync as existsSync12 } from "fs";
|
|
2159
|
+
import { dirname as dirname7, join as join16, relative as relative6, resolve as resolve10 } from "path";
|
|
2160
|
+
|
|
2161
|
+
// src/adapters/maven/maven-project.ts
|
|
2162
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
2163
|
+
import { join as join15, resolve as resolve9 } from "path";
|
|
2164
|
+
|
|
869
2165
|
// src/adapters/maven/maven-pom.ts
|
|
2166
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13, statSync } from "fs";
|
|
2167
|
+
import { dirname as dirname6, join as join14, resolve as resolve8 } from "path";
|
|
870
2168
|
function readPom(file) {
|
|
871
|
-
const source =
|
|
2169
|
+
const source = readFileSync13(file, "utf8");
|
|
872
2170
|
return parsePom(source, file);
|
|
873
2171
|
}
|
|
874
2172
|
function parsePom(source, file) {
|
|
@@ -887,7 +2185,7 @@ function parsePom(source, file) {
|
|
|
887
2185
|
}
|
|
888
2186
|
return {
|
|
889
2187
|
file,
|
|
890
|
-
dir:
|
|
2188
|
+
dir: dirname6(file),
|
|
891
2189
|
groupId: childText(project, "groupId"),
|
|
892
2190
|
artifactId: childText(project, "artifactId") ?? "unknown",
|
|
893
2191
|
version: childText(project, "version"),
|
|
@@ -916,9 +2214,9 @@ function parentPomFile(pom) {
|
|
|
916
2214
|
if (!pom.parent) return void 0;
|
|
917
2215
|
const relativePath = pom.parent.relativePath ?? "../pom.xml";
|
|
918
2216
|
if (relativePath === "") return void 0;
|
|
919
|
-
let candidate =
|
|
920
|
-
if (
|
|
921
|
-
return
|
|
2217
|
+
let candidate = resolve8(pom.dir, relativePath);
|
|
2218
|
+
if (existsSync10(candidate) && statSync(candidate).isDirectory()) candidate = join14(candidate, "pom.xml");
|
|
2219
|
+
return existsSync10(candidate) ? candidate : void 0;
|
|
922
2220
|
}
|
|
923
2221
|
function builtinProperty(name, module) {
|
|
924
2222
|
switch (name) {
|
|
@@ -993,9 +2291,9 @@ var MavenProject = class _MavenProject {
|
|
|
993
2291
|
}
|
|
994
2292
|
poms.push(pom);
|
|
995
2293
|
for (const entry of pom.modules) {
|
|
996
|
-
const target =
|
|
997
|
-
const moduleFile = target.endsWith(".xml") ? target :
|
|
998
|
-
if (!seen.has(moduleFile) &&
|
|
2294
|
+
const target = resolve9(pom.dir, entry);
|
|
2295
|
+
const moduleFile = target.endsWith(".xml") ? target : join15(target, "pom.xml");
|
|
2296
|
+
if (!seen.has(moduleFile) && existsSync11(moduleFile)) {
|
|
999
2297
|
seen.add(moduleFile);
|
|
1000
2298
|
queue.push(moduleFile);
|
|
1001
2299
|
}
|
|
@@ -1004,7 +2302,7 @@ var MavenProject = class _MavenProject {
|
|
|
1004
2302
|
return new _MavenProject(rootDir, poms);
|
|
1005
2303
|
}
|
|
1006
2304
|
pomAt(file) {
|
|
1007
|
-
return this.byFile.get(
|
|
2305
|
+
return this.byFile.get(resolve9(file));
|
|
1008
2306
|
}
|
|
1009
2307
|
/** The project followed by its local parents, stopping at the first external parent. */
|
|
1010
2308
|
chainOf(pom) {
|
|
@@ -1047,7 +2345,7 @@ var MavenProject = class _MavenProject {
|
|
|
1047
2345
|
return groupId === parent.groupId && candidate.artifactId === parent.artifactId && (version.includes("${") || expected.includes("${") || version === expected);
|
|
1048
2346
|
};
|
|
1049
2347
|
const file = parentPomFile(pom);
|
|
1050
|
-
const byPath = file ? this.byFile.get(
|
|
2348
|
+
const byPath = file ? this.byFile.get(resolve9(file)) : void 0;
|
|
1051
2349
|
return matches(byPath) ? byPath : void 0;
|
|
1052
2350
|
}
|
|
1053
2351
|
/**
|
|
@@ -1064,8 +2362,8 @@ var MavenProject = class _MavenProject {
|
|
|
1064
2362
|
if (this.aggregators.has(pom.file)) continue;
|
|
1065
2363
|
this.aggregators.set(pom.file, aggregator.dir);
|
|
1066
2364
|
for (const entry of pom.modules) {
|
|
1067
|
-
const target =
|
|
1068
|
-
const file = target.endsWith(".xml") ? target :
|
|
2365
|
+
const target = resolve9(pom.dir, entry);
|
|
2366
|
+
const file = target.endsWith(".xml") ? target : join15(target, "pom.xml");
|
|
1069
2367
|
const modulePom = this.byFile.get(file);
|
|
1070
2368
|
if (modulePom) queue.push(modulePom);
|
|
1071
2369
|
}
|
|
@@ -1075,23 +2373,23 @@ var MavenProject = class _MavenProject {
|
|
|
1075
2373
|
};
|
|
1076
2374
|
function findPomFiles(rootDir) {
|
|
1077
2375
|
const results = [];
|
|
1078
|
-
const rootPom =
|
|
1079
|
-
if (
|
|
2376
|
+
const rootPom = join15(rootDir, "pom.xml");
|
|
2377
|
+
if (existsSync11(rootPom)) results.push(resolve9(rootPom));
|
|
1080
2378
|
const walk = (dir, depth) => {
|
|
1081
2379
|
if (depth > MAX_DEPTH) return;
|
|
1082
|
-
let
|
|
2380
|
+
let entries2;
|
|
1083
2381
|
try {
|
|
1084
|
-
|
|
2382
|
+
entries2 = readdirSync2(dir, { withFileTypes: true });
|
|
1085
2383
|
} catch {
|
|
1086
2384
|
return;
|
|
1087
2385
|
}
|
|
1088
|
-
const isProject =
|
|
1089
|
-
for (const entry of
|
|
2386
|
+
const isProject = existsSync11(join15(dir, "pom.xml"));
|
|
2387
|
+
for (const entry of entries2) {
|
|
1090
2388
|
if (!entry.isDirectory() || entry.name.startsWith(".") || SKIPPED_DIRS.has(entry.name)) continue;
|
|
1091
2389
|
if (isProject && entry.name === "src") continue;
|
|
1092
|
-
const subDir =
|
|
1093
|
-
const pom =
|
|
1094
|
-
if (
|
|
2390
|
+
const subDir = join15(dir, entry.name);
|
|
2391
|
+
const pom = join15(subDir, "pom.xml");
|
|
2392
|
+
if (existsSync11(pom) && statSync2(pom).isFile()) results.push(resolve9(pom));
|
|
1095
2393
|
walk(subDir, depth + 1);
|
|
1096
2394
|
}
|
|
1097
2395
|
};
|
|
@@ -1106,9 +2404,9 @@ var MavenModuleDiscoveryAdapter = class {
|
|
|
1106
2404
|
* the POM above until there is none: the top of the connected POM chain.
|
|
1107
2405
|
*/
|
|
1108
2406
|
findRoot(startDir, boundaryDir) {
|
|
1109
|
-
let dir =
|
|
1110
|
-
if (!
|
|
1111
|
-
const boundary = boundaryDir ?
|
|
2407
|
+
let dir = resolve10(startDir);
|
|
2408
|
+
if (!existsSync12(join16(dir, "pom.xml"))) return void 0;
|
|
2409
|
+
const boundary = boundaryDir ? resolve10(boundaryDir) : void 0;
|
|
1112
2410
|
for (; ; ) {
|
|
1113
2411
|
const above = pomAbove(dir, boundary);
|
|
1114
2412
|
if (!above) return dir;
|
|
@@ -1119,7 +2417,7 @@ var MavenModuleDiscoveryAdapter = class {
|
|
|
1119
2417
|
const project = MavenProject.load(rootDir);
|
|
1120
2418
|
const modules = project.poms.map((pom) => {
|
|
1121
2419
|
const coordinates = project.coordinatesOf(pom);
|
|
1122
|
-
const relPath =
|
|
2420
|
+
const relPath = relative6(rootDir, pom.dir).replace(/\\/g, "/");
|
|
1123
2421
|
return {
|
|
1124
2422
|
id: coordinates.groupId ? `${coordinates.groupId}:${coordinates.artifactId}` : coordinates.artifactId,
|
|
1125
2423
|
name: pom.name ? interpolate(pom.name, project.chainOf(pom).poms) : pom.artifactId,
|
|
@@ -1146,7 +2444,7 @@ function ancestorsWithin(dir, boundary) {
|
|
|
1146
2444
|
const result = [];
|
|
1147
2445
|
let current = dir;
|
|
1148
2446
|
while (current !== boundary) {
|
|
1149
|
-
const parent =
|
|
2447
|
+
const parent = dirname7(current);
|
|
1150
2448
|
if (parent === current) break;
|
|
1151
2449
|
result.push(parent);
|
|
1152
2450
|
current = parent;
|
|
@@ -1154,22 +2452,22 @@ function ancestorsWithin(dir, boundary) {
|
|
|
1154
2452
|
return result;
|
|
1155
2453
|
}
|
|
1156
2454
|
function pomAbove(dir, boundary) {
|
|
1157
|
-
const pom = tryReadPom(
|
|
2455
|
+
const pom = tryReadPom(join16(dir, "pom.xml"));
|
|
1158
2456
|
if (!pom) return void 0;
|
|
1159
2457
|
const ancestors = ancestorsWithin(dir, boundary);
|
|
1160
2458
|
const found = [];
|
|
1161
2459
|
const parentFile = parentPomFile(pom);
|
|
1162
|
-
if (parentFile && pom.parent && ancestors.includes(
|
|
2460
|
+
if (parentFile && pom.parent && ancestors.includes(dirname7(parentFile))) {
|
|
1163
2461
|
const parent = tryReadPom(parentFile);
|
|
1164
2462
|
const groupId = parent?.groupId ?? parent?.parent?.groupId;
|
|
1165
2463
|
if (parent && parent.artifactId === pom.parent.artifactId && groupId === pom.parent.groupId) {
|
|
1166
|
-
found.push(
|
|
2464
|
+
found.push(dirname7(parentFile));
|
|
1167
2465
|
}
|
|
1168
2466
|
}
|
|
1169
|
-
const ownFile =
|
|
2467
|
+
const ownFile = join16(dir, "pom.xml");
|
|
1170
2468
|
const aggregator = ancestors.find(
|
|
1171
|
-
(ancestor) => tryReadPom(
|
|
1172
|
-
const target =
|
|
2469
|
+
(ancestor) => tryReadPom(join16(ancestor, "pom.xml"))?.modules.some((entry) => {
|
|
2470
|
+
const target = resolve10(ancestor, entry);
|
|
1173
2471
|
return target === dir || target === ownFile;
|
|
1174
2472
|
})
|
|
1175
2473
|
);
|
|
@@ -1178,12 +2476,12 @@ function pomAbove(dir, boundary) {
|
|
|
1178
2476
|
}
|
|
1179
2477
|
|
|
1180
2478
|
// src/adapters/maven/maven-registry.ts
|
|
1181
|
-
import { existsSync as
|
|
1182
|
-
import { join as
|
|
2479
|
+
import { existsSync as existsSync15 } from "fs";
|
|
2480
|
+
import { join as join19 } from "path";
|
|
1183
2481
|
|
|
1184
2482
|
// src/adapters/maven/maven-command.ts
|
|
1185
|
-
import { existsSync as
|
|
1186
|
-
import { dirname as
|
|
2483
|
+
import { existsSync as existsSync13 } from "fs";
|
|
2484
|
+
import { dirname as dirname8, isAbsolute as isAbsolute2, join as join17, relative as relative7, resolve as resolve11, sep } from "path";
|
|
1187
2485
|
var MavenUnavailableError = class extends Error {
|
|
1188
2486
|
constructor() {
|
|
1189
2487
|
super("Maven is not available: no Maven Wrapper (mvnw) in the project and no `mvn` on the PATH.");
|
|
@@ -1203,27 +2501,27 @@ var WRAPPER = IS_WINDOWS2 ? "mvnw.cmd" : "mvnw";
|
|
|
1203
2501
|
var BATCH_ARGS = ["-B", "--no-transfer-progress"];
|
|
1204
2502
|
var mvnOnPath;
|
|
1205
2503
|
function findMavenWrapper(fromDir, rootDir) {
|
|
1206
|
-
let dir =
|
|
1207
|
-
const root =
|
|
2504
|
+
let dir = resolve11(fromDir);
|
|
2505
|
+
const root = resolve11(rootDir);
|
|
1208
2506
|
for (; ; ) {
|
|
1209
|
-
const candidate =
|
|
1210
|
-
if (
|
|
1211
|
-
if (dir === root ||
|
|
1212
|
-
const parent =
|
|
2507
|
+
const candidate = join17(dir, WRAPPER);
|
|
2508
|
+
if (existsSync13(candidate)) return candidate;
|
|
2509
|
+
if (dir === root || relative7(root, dir).startsWith("..")) return void 0;
|
|
2510
|
+
const parent = dirname8(dir);
|
|
1213
2511
|
if (parent === dir) return void 0;
|
|
1214
2512
|
dir = parent;
|
|
1215
2513
|
}
|
|
1216
2514
|
}
|
|
1217
2515
|
function relativeToCwd(cwd, file) {
|
|
1218
|
-
const path =
|
|
1219
|
-
return
|
|
2516
|
+
const path = relative7(cwd, file);
|
|
2517
|
+
return isAbsolute2(path) ? file : path || ".";
|
|
1220
2518
|
}
|
|
1221
2519
|
async function runMaven(args, options) {
|
|
1222
2520
|
const wrapper = findMavenWrapper(options.cwd, options.rootDir);
|
|
1223
2521
|
const run = { cwd: options.cwd, timeoutMs: options.timeoutMs };
|
|
1224
2522
|
if (wrapper) {
|
|
1225
2523
|
const relativeWrapper = relativeToCwd(options.cwd, wrapper);
|
|
1226
|
-
const path =
|
|
2524
|
+
const path = isAbsolute2(relativeWrapper) || relativeWrapper.includes(sep) ? relativeWrapper : `.${sep}${relativeWrapper}`;
|
|
1227
2525
|
return IS_WINDOWS2 ? runCommand(path, [...BATCH_ARGS, ...args], run) : runCommand("sh", [path, ...BATCH_ARGS, ...args], run);
|
|
1228
2526
|
}
|
|
1229
2527
|
mvnOnPath ??= runCommand("mvn", ["-v"], { cwd: options.cwd, timeoutMs: 3e4 }).then(
|
|
@@ -1240,8 +2538,8 @@ function mavenErrors(output) {
|
|
|
1240
2538
|
|
|
1241
2539
|
// src/adapters/maven/maven-resolution.ts
|
|
1242
2540
|
import { randomUUID } from "crypto";
|
|
1243
|
-
import { existsSync as
|
|
1244
|
-
import { join as
|
|
2541
|
+
import { existsSync as existsSync14, mkdirSync, readFileSync as readFileSync14, readdirSync as readdirSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2542
|
+
import { join as join18 } from "path";
|
|
1245
2543
|
var DEFAULT_MAVEN_PLUGINS = { help: "3.5.2", versions: "2.22.0" };
|
|
1246
2544
|
var HELP_PLUGIN_ID = "org.apache.maven.plugins:maven-help-plugin";
|
|
1247
2545
|
var VERSIONS_PLUGIN_ID = "org.codehaus.mojo:versions-maven-plugin";
|
|
@@ -1273,21 +2571,21 @@ var ScratchDirectory = class {
|
|
|
1273
2571
|
target;
|
|
1274
2572
|
createdTarget;
|
|
1275
2573
|
constructor(moduleDir) {
|
|
1276
|
-
this.target =
|
|
1277
|
-
this.createdTarget = !
|
|
1278
|
-
this.path =
|
|
2574
|
+
this.target = join18(moduleDir, "target");
|
|
2575
|
+
this.createdTarget = !existsSync14(this.target);
|
|
2576
|
+
this.path = join18(this.target, `.elevate-${randomUUID().slice(0, 8)}`);
|
|
1279
2577
|
mkdirSync(this.path, { recursive: true });
|
|
1280
2578
|
}
|
|
1281
2579
|
dispose() {
|
|
1282
2580
|
rmSync(this.path, { recursive: true, force: true });
|
|
1283
2581
|
try {
|
|
1284
|
-
if (this.createdTarget &&
|
|
2582
|
+
if (this.createdTarget && readdirSync3(this.target).length === 0) rmSync(this.target, { recursive: true });
|
|
1285
2583
|
} catch {
|
|
1286
2584
|
}
|
|
1287
2585
|
}
|
|
1288
2586
|
};
|
|
1289
2587
|
async function readEffectivePoms(pomFile, moduleDir, rootDir, scratch, plugins, recursive = false) {
|
|
1290
|
-
const output =
|
|
2588
|
+
const output = join18(scratch.path, "effective-pom.xml");
|
|
1291
2589
|
const args = [
|
|
1292
2590
|
"-f",
|
|
1293
2591
|
relativeToCwd(moduleDir, pomFile),
|
|
@@ -1299,7 +2597,7 @@ async function readEffectivePoms(pomFile, moduleDir, rootDir, scratch, plugins,
|
|
|
1299
2597
|
rootDir,
|
|
1300
2598
|
timeoutMs: MAVEN_TIMEOUT_MS
|
|
1301
2599
|
});
|
|
1302
|
-
if (result.exitCode !== 0 || !
|
|
2600
|
+
if (result.exitCode !== 0 || !existsSync14(output)) {
|
|
1303
2601
|
const text = `${result.stdout}
|
|
1304
2602
|
${result.stderr}`;
|
|
1305
2603
|
if (isPluginUnavailable(text, HELP_PLUGIN_ID)) {
|
|
@@ -1311,7 +2609,7 @@ ${result.stderr}`;
|
|
|
1311
2609
|
throw new MavenCommandError(`Maven could not build the effective POM:
|
|
1312
2610
|
${mavenErrors(text)}`, text);
|
|
1313
2611
|
}
|
|
1314
|
-
return parseEffectivePoms(
|
|
2612
|
+
return parseEffectivePoms(readFileSync14(output, "utf8"));
|
|
1315
2613
|
}
|
|
1316
2614
|
function parseEffectivePoms(source) {
|
|
1317
2615
|
const root = parseXml(source);
|
|
@@ -1323,7 +2621,7 @@ function parseEffectivePoms(source) {
|
|
|
1323
2621
|
return models;
|
|
1324
2622
|
}
|
|
1325
2623
|
function mergeRepositories(models) {
|
|
1326
|
-
const
|
|
2624
|
+
const merge2 = (name, pick) => {
|
|
1327
2625
|
const byId = /* @__PURE__ */ new Map();
|
|
1328
2626
|
for (const element of models.map(pick)) {
|
|
1329
2627
|
for (const repository of element?.children ?? []) {
|
|
@@ -1336,8 +2634,8 @@ function mergeRepositories(models) {
|
|
|
1336
2634
|
return {
|
|
1337
2635
|
dependencies: [],
|
|
1338
2636
|
managedDependencies: [],
|
|
1339
|
-
repositories:
|
|
1340
|
-
pluginRepositories:
|
|
2637
|
+
repositories: merge2("repositories", (m) => m.repositories),
|
|
2638
|
+
pluginRepositories: merge2("pluginRepositories", (m) => m.pluginRepositories)
|
|
1341
2639
|
};
|
|
1342
2640
|
}
|
|
1343
2641
|
function toEffectiveModel(project) {
|
|
@@ -1360,7 +2658,7 @@ function readEffectiveDependencies(container) {
|
|
|
1360
2658
|
}
|
|
1361
2659
|
function buildProbePom(artifacts, model) {
|
|
1362
2660
|
const seen = /* @__PURE__ */ new Set();
|
|
1363
|
-
const
|
|
2661
|
+
const entries2 = [];
|
|
1364
2662
|
for (const artifact of artifacts) {
|
|
1365
2663
|
const key = `${artifact.groupId}:${artifact.artifactId}:${artifact.type ?? "jar"}:${artifact.classifier ?? ""}`;
|
|
1366
2664
|
if (seen.has(key)) continue;
|
|
@@ -1372,7 +2670,7 @@ function buildProbePom(artifacts, model) {
|
|
|
1372
2670
|
artifact.type && artifact.type !== "jar" ? `<type>${escapeXml(artifact.type)}</type>` : "",
|
|
1373
2671
|
artifact.classifier ? `<classifier>${escapeXml(artifact.classifier)}</classifier>` : ""
|
|
1374
2672
|
].filter(Boolean);
|
|
1375
|
-
|
|
2673
|
+
entries2.push(` <dependency>${fields.join("")}</dependency>`);
|
|
1376
2674
|
}
|
|
1377
2675
|
const repositories = [model.repositories, model.pluginRepositories].filter((element) => element !== void 0).map((element) => serializeXml(element, " ")).join("\n");
|
|
1378
2676
|
return [
|
|
@@ -1386,7 +2684,7 @@ function buildProbePom(artifacts, model) {
|
|
|
1386
2684
|
repositories,
|
|
1387
2685
|
" <dependencyManagement>",
|
|
1388
2686
|
" <dependencies>",
|
|
1389
|
-
...
|
|
2687
|
+
...entries2,
|
|
1390
2688
|
" </dependencies>",
|
|
1391
2689
|
" </dependencyManagement>",
|
|
1392
2690
|
"</project>",
|
|
@@ -1395,10 +2693,10 @@ function buildProbePom(artifacts, model) {
|
|
|
1395
2693
|
}
|
|
1396
2694
|
async function queryNewerVersions(artifacts, model, moduleDir, rootDir, scratch, plugins) {
|
|
1397
2695
|
if (artifacts.length === 0) return { newerVersions: /* @__PURE__ */ new Map(), failedLookups: /* @__PURE__ */ new Set() };
|
|
1398
|
-
const probeDir =
|
|
2696
|
+
const probeDir = join18(scratch.path, "probe");
|
|
1399
2697
|
mkdirSync(probeDir, { recursive: true });
|
|
1400
|
-
const probePom =
|
|
1401
|
-
|
|
2698
|
+
const probePom = join18(probeDir, "pom.xml");
|
|
2699
|
+
writeFileSync3(probePom, buildProbePom(artifacts, model), "utf8");
|
|
1402
2700
|
const result = await runMaven(
|
|
1403
2701
|
[
|
|
1404
2702
|
"-N",
|
|
@@ -1412,8 +2710,8 @@ async function queryNewerVersions(artifacts, model, moduleDir, rootDir, scratch,
|
|
|
1412
2710
|
);
|
|
1413
2711
|
const log = `${result.stdout}
|
|
1414
2712
|
${result.stderr}`;
|
|
1415
|
-
const reportFile =
|
|
1416
|
-
if (result.exitCode !== 0 || !
|
|
2713
|
+
const reportFile = join18(probeDir, "target", "dependency-updates-report.xml");
|
|
2714
|
+
if (result.exitCode !== 0 || !existsSync14(reportFile)) {
|
|
1417
2715
|
if (isPluginUnavailable(log, VERSIONS_PLUGIN_ID)) {
|
|
1418
2716
|
throw new MavenPluginUnavailableError(
|
|
1419
2717
|
{ id: VERSIONS_PLUGIN_ID, version: plugins.versions, key: "versions", purpose: "look up newer versions" },
|
|
@@ -1424,7 +2722,7 @@ ${result.stderr}`;
|
|
|
1424
2722
|
${mavenErrors(log)}`, log);
|
|
1425
2723
|
}
|
|
1426
2724
|
return {
|
|
1427
|
-
newerVersions: parseUpdatesReport(
|
|
2725
|
+
newerVersions: parseUpdatesReport(readFileSync14(reportFile, "utf8")),
|
|
1428
2726
|
failedLookups: parseFailedLookups(log)
|
|
1429
2727
|
};
|
|
1430
2728
|
}
|
|
@@ -1509,18 +2807,18 @@ var MavenRegistryAdapter = class {
|
|
|
1509
2807
|
}
|
|
1510
2808
|
};
|
|
1511
2809
|
async function projectRepositories(cwd, scratch, plugins) {
|
|
1512
|
-
const rootPom =
|
|
1513
|
-
if (!
|
|
2810
|
+
const rootPom = join19(cwd, "pom.xml");
|
|
2811
|
+
if (!existsSync15(rootPom)) return NO_REPOSITORIES;
|
|
1514
2812
|
const models = await readEffectivePoms(rootPom, cwd, cwd, scratch, plugins);
|
|
1515
2813
|
return [...models.values()][0] ?? NO_REPOSITORIES;
|
|
1516
2814
|
}
|
|
1517
2815
|
|
|
1518
2816
|
// src/adapters/maven/maven-scanner.ts
|
|
1519
|
-
import { join as
|
|
1520
|
-
import
|
|
2817
|
+
import { join as join20 } from "path";
|
|
2818
|
+
import semver11 from "semver";
|
|
1521
2819
|
|
|
1522
2820
|
// src/adapters/maven/maven-locator.ts
|
|
1523
|
-
import { relative as
|
|
2821
|
+
import { relative as relative8 } from "path";
|
|
1524
2822
|
function locateDependencyVersion(chain, target, rootDir) {
|
|
1525
2823
|
for (const pom of chain) {
|
|
1526
2824
|
const dependency = findDependency(pom.dependencies, target, chain);
|
|
@@ -1601,13 +2899,13 @@ function resolveLiteral(pom, kind, versionElement, chain, target, rootDir) {
|
|
|
1601
2899
|
file,
|
|
1602
2900
|
kind: propertyName ? "property" : kind,
|
|
1603
2901
|
propertyName,
|
|
1604
|
-
displayPath:
|
|
2902
|
+
displayPath: relative8(rootDir, file).replace(/\\/g, "/")
|
|
1605
2903
|
};
|
|
1606
2904
|
return { found: true, declaration, element, value };
|
|
1607
2905
|
}
|
|
1608
2906
|
|
|
1609
2907
|
// src/adapters/maven/maven-scanner.ts
|
|
1610
|
-
var
|
|
2908
|
+
var DIFF_ORDER3 = { major: 0, minor: 1, patch: 2 };
|
|
1611
2909
|
var MavenDependencyAdapter = class {
|
|
1612
2910
|
constructor(plugins = DEFAULT_MAVEN_PLUGINS) {
|
|
1613
2911
|
this.plugins = plugins;
|
|
@@ -1661,14 +2959,14 @@ function groupModules(project, modules) {
|
|
|
1661
2959
|
if (module.aggregatorDir) reactorSizes.set(module.aggregatorDir, (reactorSizes.get(module.aggregatorDir) ?? 0) + 1);
|
|
1662
2960
|
}
|
|
1663
2961
|
for (const module of modules) {
|
|
1664
|
-
const pom = project.pomAt(
|
|
2962
|
+
const pom = project.pomAt(join20(module.path, "pom.xml"));
|
|
1665
2963
|
if (!pom) continue;
|
|
1666
2964
|
const reactor = module.aggregatorDir;
|
|
1667
2965
|
const batched = reactor !== void 0 && (reactorSizes.get(reactor) ?? 0) > 1;
|
|
1668
2966
|
const key = batched ? `reactor:${reactor}` : `module:${module.path}`;
|
|
1669
2967
|
const group = groups.get(key) ?? {
|
|
1670
2968
|
dir: batched ? reactor : module.path,
|
|
1671
|
-
aggregatorPom: batched ?
|
|
2969
|
+
aggregatorPom: batched ? join20(reactor, "pom.xml") : void 0,
|
|
1672
2970
|
members: []
|
|
1673
2971
|
};
|
|
1674
2972
|
group.members.push({ module, pom });
|
|
@@ -1757,7 +3055,7 @@ function finishModule(plan, report, channel) {
|
|
|
1757
3055
|
collect2(updateCandidate(subject, latest, newer, origin), candidates, skipped);
|
|
1758
3056
|
}
|
|
1759
3057
|
markSharedDeclarations(candidates);
|
|
1760
|
-
candidates.sort((a, b) => (
|
|
3058
|
+
candidates.sort((a, b) => (DIFF_ORDER3[a.diff] ?? 3) - (DIFF_ORDER3[b.diff] ?? 3));
|
|
1761
3059
|
return { candidates, skipped };
|
|
1762
3060
|
}
|
|
1763
3061
|
function newerThan(current, ascending) {
|
|
@@ -1788,7 +3086,7 @@ function buildCandidate(subject, target, origin, action) {
|
|
|
1788
3086
|
}
|
|
1789
3087
|
const currentClean = cleanJavaVersion(subject.version) ?? "0.0.0";
|
|
1790
3088
|
const targetClean = cleanJavaVersion(target) ?? currentClean;
|
|
1791
|
-
const diff =
|
|
3089
|
+
const diff = semver11.eq(currentClean, targetClean) ? "patch" : diffVersions(currentClean, targetClean);
|
|
1792
3090
|
const isPreRelease = isPreReleaseVersion(target);
|
|
1793
3091
|
return {
|
|
1794
3092
|
candidate: {
|
|
@@ -1836,11 +3134,11 @@ function markSharedDeclarations(candidates) {
|
|
|
1836
3134
|
}
|
|
1837
3135
|
|
|
1838
3136
|
// src/adapters/maven/maven-updater.ts
|
|
1839
|
-
import { readFileSync as
|
|
1840
|
-
import { join as
|
|
3137
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync4 } from "fs";
|
|
3138
|
+
import { join as join21, resolve as resolve12 } from "path";
|
|
1841
3139
|
var MavenUpdaterAdapter = class {
|
|
1842
3140
|
affectedFiles(module, _rootDir, updates) {
|
|
1843
|
-
const files = new Set(updates.map((u) =>
|
|
3141
|
+
const files = new Set(updates.map((u) => resolve12(u.declaration?.file ?? join21(module.path, "pom.xml"))));
|
|
1844
3142
|
return [...files];
|
|
1845
3143
|
}
|
|
1846
3144
|
async applyUpdates(module, rootDir, updates, onProgress) {
|
|
@@ -1850,13 +3148,13 @@ var MavenUpdaterAdapter = class {
|
|
|
1850
3148
|
if (!update.declaration) {
|
|
1851
3149
|
throw new InstallError(`No version declaration known for ${update.coordinate.identifier}; rescan the module.`);
|
|
1852
3150
|
}
|
|
1853
|
-
const file =
|
|
3151
|
+
const file = resolve12(update.declaration.file);
|
|
1854
3152
|
byFile.set(file, [...byFile.get(file) ?? [], update]);
|
|
1855
3153
|
}
|
|
1856
3154
|
const writes = [];
|
|
1857
3155
|
for (const [file, fileUpdates] of byFile) {
|
|
1858
3156
|
onProgress(`Preparing ${fileUpdates.length} change(s) in ${fileUpdates[0].declaration.displayPath}\u2026`);
|
|
1859
|
-
const source =
|
|
3157
|
+
const source = readFileSync15(file, "utf8");
|
|
1860
3158
|
const pom = parsePom(source, file);
|
|
1861
3159
|
const chain = project.pomAt(file) ? project.chainOf(project.pomAt(file)).poms : [pom];
|
|
1862
3160
|
const edits = collectEdits(pom, [pom, ...chain.slice(1)], fileUpdates);
|
|
@@ -1868,7 +3166,7 @@ var MavenUpdaterAdapter = class {
|
|
|
1868
3166
|
}
|
|
1869
3167
|
}
|
|
1870
3168
|
onProgress("Writing POM files\u2026");
|
|
1871
|
-
for (const write of writes)
|
|
3169
|
+
for (const write of writes) writeFileSync4(write.file, write.content, "utf8");
|
|
1872
3170
|
return {
|
|
1873
3171
|
updatedCount: updates.length,
|
|
1874
3172
|
auditMessage: `Updated ${updates.length} version declaration(s) in ${writes.length} POM file(s).`,
|
|
@@ -1926,14 +3224,14 @@ function describe(declaration) {
|
|
|
1926
3224
|
}
|
|
1927
3225
|
|
|
1928
3226
|
// src/adapters/maven/maven-verifier.ts
|
|
1929
|
-
import { dirname as
|
|
1930
|
-
var
|
|
3227
|
+
import { dirname as dirname9, join as join22, relative as relative9, resolve as resolve13 } from "path";
|
|
3228
|
+
var VERIFY_TIMEOUT_MS3 = 30 * 6e4;
|
|
1931
3229
|
var MavenVerificationAdapter = class {
|
|
1932
3230
|
async verify(module, rootDir, options = {}) {
|
|
1933
3231
|
if (options.customScript) {
|
|
1934
3232
|
const label2 = options.customLabel || options.customScript;
|
|
1935
|
-
const result = await runShellScript(options.customScript, { cwd: module.path, timeoutMs:
|
|
1936
|
-
return result.exitCode === 0 ? { status: "clean", details: "Verification script succeeded.", label: label2 } : { status: "warn", details:
|
|
3233
|
+
const result = await runShellScript(options.customScript, { cwd: module.path, timeoutMs: VERIFY_TIMEOUT_MS3 });
|
|
3234
|
+
return result.exitCode === 0 ? { status: "clean", details: "Verification script succeeded.", label: label2 } : { status: "warn", details: failureDetails2(result), label: label2 };
|
|
1937
3235
|
}
|
|
1938
3236
|
const plan = planBuild(module, rootDir, options.changedFiles ?? []);
|
|
1939
3237
|
const label = `Maven build (${["test-compile", ...plan.args].join(" ")})`;
|
|
@@ -1941,12 +3239,12 @@ var MavenVerificationAdapter = class {
|
|
|
1941
3239
|
const result = await runMaven(["-q", "test-compile", ...plan.args], {
|
|
1942
3240
|
cwd: plan.cwd,
|
|
1943
3241
|
rootDir,
|
|
1944
|
-
timeoutMs:
|
|
3242
|
+
timeoutMs: VERIFY_TIMEOUT_MS3
|
|
1945
3243
|
});
|
|
1946
3244
|
if (result.exitCode === 0) {
|
|
1947
3245
|
return { status: "clean", details: "All affected modules compile, including tests.", label };
|
|
1948
3246
|
}
|
|
1949
|
-
const details = result.timedOut ? "Maven build timed out." :
|
|
3247
|
+
const details = result.timedOut ? "Maven build timed out." : failureDetails2(result);
|
|
1950
3248
|
return { status: "warn", details, label };
|
|
1951
3249
|
} catch (err) {
|
|
1952
3250
|
if (err instanceof MavenUnavailableError) return { status: "warn", details: err.message, label };
|
|
@@ -1954,7 +3252,7 @@ var MavenVerificationAdapter = class {
|
|
|
1954
3252
|
}
|
|
1955
3253
|
}
|
|
1956
3254
|
};
|
|
1957
|
-
function
|
|
3255
|
+
function failureDetails2(result) {
|
|
1958
3256
|
const output = `${result.stdout}
|
|
1959
3257
|
${result.stderr}`.trim();
|
|
1960
3258
|
return output ? mavenErrors(output) : `Exited with code ${result.exitCode}.`;
|
|
@@ -1963,15 +3261,15 @@ function planBuild(module, rootDir, changedFiles) {
|
|
|
1963
3261
|
const aggregatorDir = module.aggregatorDir;
|
|
1964
3262
|
if (!aggregatorDir) return { cwd: module.path, args: [] };
|
|
1965
3263
|
const project = MavenProject.load(rootDir);
|
|
1966
|
-
const files = changedFiles.length > 0 ? changedFiles : [
|
|
3264
|
+
const files = changedFiles.length > 0 ? changedFiles : [join22(module.path, "pom.xml")];
|
|
1967
3265
|
const selected = /* @__PURE__ */ new Set();
|
|
1968
3266
|
for (const file of files) {
|
|
1969
3267
|
const pom = project.pomAt(file);
|
|
1970
|
-
const dir =
|
|
1971
|
-
if (!pom || project.aggregatorDirOf(pom) !== aggregatorDir || dir ===
|
|
3268
|
+
const dir = resolve13(dirname9(file));
|
|
3269
|
+
if (!pom || project.aggregatorDirOf(pom) !== aggregatorDir || dir === resolve13(aggregatorDir)) {
|
|
1972
3270
|
return { cwd: aggregatorDir, args: [] };
|
|
1973
3271
|
}
|
|
1974
|
-
selected.add(
|
|
3272
|
+
selected.add(relative9(aggregatorDir, dir).replace(/\\/g, "/"));
|
|
1975
3273
|
}
|
|
1976
3274
|
return { cwd: aggregatorDir, args: ["-pl", [...selected].join(","), "-amd"] };
|
|
1977
3275
|
}
|
|
@@ -1982,6 +3280,7 @@ var MavenEcosystemStrategy = class {
|
|
|
1982
3280
|
displayName = "Java / Maven";
|
|
1983
3281
|
icon = "\u2615";
|
|
1984
3282
|
manifestFile = "pom.xml";
|
|
3283
|
+
versioning = mavenVersionPolicy;
|
|
1985
3284
|
discovery = new MavenModuleDiscoveryAdapter();
|
|
1986
3285
|
registry;
|
|
1987
3286
|
reader;
|
|
@@ -2019,6 +3318,8 @@ var EcosystemFactory = class {
|
|
|
2019
3318
|
if (!strategy) {
|
|
2020
3319
|
if (ecosystem === "maven") {
|
|
2021
3320
|
strategy = new MavenEcosystemStrategy(this.settings.mavenPlugins);
|
|
3321
|
+
} else if (ecosystem === "cargo") {
|
|
3322
|
+
strategy = new CargoEcosystemStrategy();
|
|
2022
3323
|
} else {
|
|
2023
3324
|
strategy = new NpmEcosystemStrategy();
|
|
2024
3325
|
}
|
|
@@ -2028,18 +3329,23 @@ var EcosystemFactory = class {
|
|
|
2028
3329
|
}
|
|
2029
3330
|
/** All supported ecosystems. */
|
|
2030
3331
|
static getAvailableEcosystems() {
|
|
2031
|
-
return ["npm", "maven"];
|
|
3332
|
+
return ["npm", "maven", "cargo"];
|
|
3333
|
+
}
|
|
3334
|
+
/** The ecosystem after `current`, for the dashboard's ecosystem switch. */
|
|
3335
|
+
static next(current) {
|
|
3336
|
+
const all = this.getAvailableEcosystems();
|
|
3337
|
+
return all[(all.indexOf(current) + 1) % all.length];
|
|
2032
3338
|
}
|
|
2033
3339
|
};
|
|
2034
3340
|
|
|
2035
3341
|
// src/config.ts
|
|
2036
3342
|
function findRepositoryRoot(startDir) {
|
|
2037
|
-
const start =
|
|
3343
|
+
const start = resolve14(startDir);
|
|
2038
3344
|
const boundary = findCheckoutBoundary(start);
|
|
2039
3345
|
let dir = start;
|
|
2040
3346
|
for (; ; ) {
|
|
2041
|
-
if (
|
|
2042
|
-
const parent =
|
|
3347
|
+
if (existsSync16(join23(dir, "elevate.config.json"))) return dir;
|
|
3348
|
+
const parent = dirname10(dir);
|
|
2043
3349
|
if (dir === boundary || parent === dir) break;
|
|
2044
3350
|
dir = parent;
|
|
2045
3351
|
}
|
|
@@ -2104,15 +3410,15 @@ function resolveConfig(rootDir, file, source = "elevate.config.json") {
|
|
|
2104
3410
|
}
|
|
2105
3411
|
function loadConfig2(startDir = process.cwd()) {
|
|
2106
3412
|
const rootDir = findRepositoryRoot(startDir);
|
|
2107
|
-
const configPath =
|
|
3413
|
+
const configPath = join23(rootDir, "elevate.config.json");
|
|
2108
3414
|
let file = {};
|
|
2109
|
-
if (
|
|
3415
|
+
if (existsSync16(configPath)) {
|
|
2110
3416
|
const unreadable = (detail) => new ConfigError(
|
|
2111
3417
|
`Cannot read elevate.config.json: ${detail} (${configPath}). Fix the file or remove it; ${NO_CONTINUE}`
|
|
2112
3418
|
);
|
|
2113
3419
|
let parsed;
|
|
2114
3420
|
try {
|
|
2115
|
-
parsed = JSON.parse(
|
|
3421
|
+
parsed = JSON.parse(readFileSync16(configPath, "utf8").replace(/^/, ""));
|
|
2116
3422
|
} catch (err) {
|
|
2117
3423
|
throw unreadable(err instanceof Error ? err.message : String(err));
|
|
2118
3424
|
}
|
|
@@ -2161,7 +3467,9 @@ var de = {
|
|
|
2161
3467
|
"lookup-failed": "Abfrage der Registry fehlgeschlagen",
|
|
2162
3468
|
"managed-externally": "Version wird au\xDFerhalb dieses Repos verwaltet",
|
|
2163
3469
|
"declaration-mismatch": "Ort der Versionsangabe ist nicht eindeutig",
|
|
2164
|
-
"invalid-name": "ung\xFCltiger Paketname"
|
|
3470
|
+
"invalid-name": "ung\xFCltiger Paketname",
|
|
3471
|
+
"not-linked": "Workspace-Paket, das pnpm ohne das workspace:-Protokoll nicht verlinkt",
|
|
3472
|
+
"catalog-unresolved": "Catalog-Eintrag konnte nicht aufgel\xF6st werden"
|
|
2165
3473
|
})[reason]
|
|
2166
3474
|
},
|
|
2167
3475
|
badges: {
|
|
@@ -2171,7 +3479,8 @@ var de = {
|
|
|
2171
3479
|
dev: "(dev/test)",
|
|
2172
3480
|
prod: "(prod/compile)",
|
|
2173
3481
|
align: "[Angleichen]",
|
|
2174
|
-
internal: "[Intern]"
|
|
3482
|
+
internal: "[Intern]",
|
|
3483
|
+
shared: (count) => `[+${count} Crates]`
|
|
2175
3484
|
},
|
|
2176
3485
|
status: {
|
|
2177
3486
|
selectedOf: (selected, total) => `${selected} von ${total}`,
|
|
@@ -2200,6 +3509,7 @@ var de = {
|
|
|
2200
3509
|
hint: "[\u2191/\u2193/j/k] Navigieren \u2502 [Enter] Ausw\xE4hlen \u2502 [Esc] Abbrechen \u2502 [Backspace] Filter l\xF6schen",
|
|
2201
3510
|
latestBadge: "LATEST",
|
|
2202
3511
|
currentBadge: "AKTUELL",
|
|
3512
|
+
alsoChanges: (names) => `\xC4ndert auch: ${names.join(", ")}`,
|
|
2203
3513
|
customBadge: "MANUELL"
|
|
2204
3514
|
},
|
|
2205
3515
|
updating: {
|
|
@@ -2228,6 +3538,7 @@ var de = {
|
|
|
2228
3538
|
scanningChirps: ["*such*", "pr\xFCfe...", "oh, Updates?", "einen Moment!"],
|
|
2229
3539
|
updatingChirps: ["*schuft*", "baue Code...", "gleich fertig!", "arbeite emsig..."],
|
|
2230
3540
|
successChirps: ["hurra! \u{1F389}", "alles aktuell!", "top gepflegt! \u{1FAB6}", "super gemacht!"],
|
|
3541
|
+
cargoChirps: ["*krabbel* \u{1F980}", "cargo check l\xE4uft durch!", "Borrow Checker nickt!", "furchtlose Updates!"],
|
|
2231
3542
|
mavenChirps: ["*kaffee schl\xFCrf* \u2615", "Spring Boot l\xE4uft!", "Java 25 rockt!", "sauber gebaut!"]
|
|
2232
3543
|
},
|
|
2233
3544
|
splash: {
|
|
@@ -2267,7 +3578,9 @@ var en = {
|
|
|
2267
3578
|
"lookup-failed": "registry lookup failed",
|
|
2268
3579
|
"managed-externally": "version is managed outside this repository",
|
|
2269
3580
|
"declaration-mismatch": "version location is ambiguous",
|
|
2270
|
-
"invalid-name": "invalid package name"
|
|
3581
|
+
"invalid-name": "invalid package name",
|
|
3582
|
+
"not-linked": "workspace package that pnpm does not link without the workspace: protocol",
|
|
3583
|
+
"catalog-unresolved": "catalog entry could not be resolved"
|
|
2271
3584
|
})[reason]
|
|
2272
3585
|
},
|
|
2273
3586
|
badges: {
|
|
@@ -2277,7 +3590,8 @@ var en = {
|
|
|
2277
3590
|
dev: "(dev/test)",
|
|
2278
3591
|
prod: "(prod/compile)",
|
|
2279
3592
|
align: "[Align]",
|
|
2280
|
-
internal: "[Internal]"
|
|
3593
|
+
internal: "[Internal]",
|
|
3594
|
+
shared: (count) => `[+${count} crates]`
|
|
2281
3595
|
},
|
|
2282
3596
|
status: {
|
|
2283
3597
|
selectedOf: (selected, total) => `${selected} of ${total}`,
|
|
@@ -2306,6 +3620,7 @@ var en = {
|
|
|
2306
3620
|
hint: "[\u2191/\u2193/j/k] Navigate \u2502 [Enter] Select \u2502 [Esc] Cancel \u2502 [Backspace] Clear filter",
|
|
2307
3621
|
latestBadge: "LATEST",
|
|
2308
3622
|
currentBadge: "CURRENT",
|
|
3623
|
+
alsoChanges: (names) => `Also changes: ${names.join(", ")}`,
|
|
2309
3624
|
customBadge: "MANUAL"
|
|
2310
3625
|
},
|
|
2311
3626
|
updating: {
|
|
@@ -2334,6 +3649,7 @@ var en = {
|
|
|
2334
3649
|
scanningChirps: ["*peeking*", "checking...", "ooh, updates?", "just a sec!"],
|
|
2335
3650
|
updatingChirps: ["*working hard*", "building...", "almost there!", "packing modules..."],
|
|
2336
3651
|
successChirps: ["hooray! \u{1F389}", "all updated!", "looking sharp! \u{1FAB6}", "great job!"],
|
|
3652
|
+
cargoChirps: ["*crab walk* \u{1F980}", "cargo check passes!", "borrow checker approves!", "fearless updates!"],
|
|
2337
3653
|
mavenChirps: ["*sipping coffee* \u2615", "Spring Boot rocks!", "Java 25 is fly!", "clean build!"]
|
|
2338
3654
|
},
|
|
2339
3655
|
splash: {
|
|
@@ -2370,48 +3686,12 @@ function useI18n(initialLocale = "de") {
|
|
|
2370
3686
|
|
|
2371
3687
|
// src/hooks/use-workspaces.ts
|
|
2372
3688
|
import { useState as useState2, useEffect, useCallback as useCallback2 } from "react";
|
|
2373
|
-
function useWorkspaces(strategy, rootDir) {
|
|
2374
|
-
const [modules, setModules] = useState2([]);
|
|
2375
|
-
const [loading, setLoading] = useState2(true);
|
|
2376
|
-
const [activeIndex, setActiveIndex] = useState2(0);
|
|
2377
|
-
const reload = useCallback2(async () => {
|
|
2378
|
-
setLoading(true);
|
|
2379
|
-
setModules([]);
|
|
2380
|
-
setActiveIndex(0);
|
|
2381
|
-
try {
|
|
2382
|
-
setModules(await strategy.discovery.discover(rootDir));
|
|
2383
|
-
} catch {
|
|
2384
|
-
setModules([]);
|
|
2385
|
-
} finally {
|
|
2386
|
-
setLoading(false);
|
|
2387
|
-
}
|
|
2388
|
-
}, [strategy, rootDir]);
|
|
2389
|
-
useEffect(() => {
|
|
2390
|
-
reload();
|
|
2391
|
-
}, [reload]);
|
|
2392
|
-
const active = modules[activeIndex] || {
|
|
2393
|
-
id: "empty",
|
|
2394
|
-
name: "No module found",
|
|
2395
|
-
path: rootDir,
|
|
2396
|
-
relPath: "Root",
|
|
2397
|
-
ecosystem: strategy.ecosystem,
|
|
2398
|
-
isRoot: true
|
|
2399
|
-
};
|
|
2400
|
-
return {
|
|
2401
|
-
modules,
|
|
2402
|
-
active,
|
|
2403
|
-
activeIndex,
|
|
2404
|
-
setActiveIndex,
|
|
2405
|
-
loading,
|
|
2406
|
-
reload
|
|
2407
|
-
};
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
// src/hooks/use-packages.ts
|
|
2411
|
-
import { useState as useState3, useEffect as useEffect2, useMemo as useMemo2, useCallback as useCallback3, useRef } from "react";
|
|
2412
|
-
import semver5 from "semver";
|
|
2413
3689
|
|
|
2414
3690
|
// src/application/scan.ts
|
|
3691
|
+
async function discoverModules(strategy, rootDir) {
|
|
3692
|
+
const modules = await strategy.discovery.discover(rootDir);
|
|
3693
|
+
return { modules, diagnostics: strategy.discovery.diagnostics?.() ?? [] };
|
|
3694
|
+
}
|
|
2415
3695
|
function createScanContext(modules, config, channel) {
|
|
2416
3696
|
return {
|
|
2417
3697
|
rootDir: config.rootDir,
|
|
@@ -2461,12 +3741,62 @@ function candidateToJson(u) {
|
|
|
2461
3741
|
scope: u.scope,
|
|
2462
3742
|
isPreRelease: u.isPreRelease ?? false,
|
|
2463
3743
|
preReleaseTag: u.preReleaseTag,
|
|
2464
|
-
declaredIn: u.declaration ? {
|
|
3744
|
+
declaredIn: u.declaration ? {
|
|
3745
|
+
file: u.declaration.displayPath,
|
|
3746
|
+
kind: u.declaration.kind,
|
|
3747
|
+
property: u.declaration.propertyName,
|
|
3748
|
+
catalog: u.declaration.catalogName
|
|
3749
|
+
} : void 0,
|
|
2465
3750
|
sharedWith: u.sharedWith
|
|
2466
3751
|
};
|
|
2467
3752
|
}
|
|
2468
3753
|
|
|
3754
|
+
// src/hooks/use-workspaces.ts
|
|
3755
|
+
function useWorkspaces(strategy, rootDir) {
|
|
3756
|
+
const [modules, setModules] = useState2([]);
|
|
3757
|
+
const [loading, setLoading] = useState2(true);
|
|
3758
|
+
const [activeIndex, setActiveIndex] = useState2(0);
|
|
3759
|
+
const [diagnostics, setDiagnostics] = useState2([]);
|
|
3760
|
+
const reload = useCallback2(async () => {
|
|
3761
|
+
setLoading(true);
|
|
3762
|
+
setModules([]);
|
|
3763
|
+
setActiveIndex(0);
|
|
3764
|
+
setDiagnostics([]);
|
|
3765
|
+
try {
|
|
3766
|
+
const found = await discoverModules(strategy, rootDir);
|
|
3767
|
+
setModules(found.modules);
|
|
3768
|
+
setDiagnostics(found.diagnostics);
|
|
3769
|
+
} catch (err) {
|
|
3770
|
+
setModules([]);
|
|
3771
|
+
setDiagnostics([err instanceof Error ? err.message : String(err)]);
|
|
3772
|
+
} finally {
|
|
3773
|
+
setLoading(false);
|
|
3774
|
+
}
|
|
3775
|
+
}, [strategy, rootDir]);
|
|
3776
|
+
useEffect(() => {
|
|
3777
|
+
reload();
|
|
3778
|
+
}, [reload]);
|
|
3779
|
+
const active = modules[activeIndex] || {
|
|
3780
|
+
id: "empty",
|
|
3781
|
+
name: "No module found",
|
|
3782
|
+
path: rootDir,
|
|
3783
|
+
relPath: "Root",
|
|
3784
|
+
ecosystem: strategy.ecosystem,
|
|
3785
|
+
isRoot: true
|
|
3786
|
+
};
|
|
3787
|
+
return {
|
|
3788
|
+
modules,
|
|
3789
|
+
active,
|
|
3790
|
+
activeIndex,
|
|
3791
|
+
setActiveIndex,
|
|
3792
|
+
loading,
|
|
3793
|
+
diagnostics,
|
|
3794
|
+
reload
|
|
3795
|
+
};
|
|
3796
|
+
}
|
|
3797
|
+
|
|
2469
3798
|
// src/hooks/use-packages.ts
|
|
3799
|
+
import { useState as useState3, useEffect as useEffect2, useMemo as useMemo2, useCallback as useCallback3, useRef } from "react";
|
|
2470
3800
|
function usePackages(strategy, module, modules, config, channel = "stable") {
|
|
2471
3801
|
const [candidates, setCandidates] = useState3([]);
|
|
2472
3802
|
const [skipped, setSkipped] = useState3([]);
|
|
@@ -2533,15 +3863,12 @@ function usePackages(strategy, module, modules, config, channel = "stable") {
|
|
|
2533
3863
|
setCandidates(
|
|
2534
3864
|
(prev) => prev.map((c) => {
|
|
2535
3865
|
if (c.coordinate.identifier !== identifier) return c;
|
|
2536
|
-
const
|
|
2537
|
-
|
|
2538
|
-
if (c.currentClean && target && semver5.valid(c.currentClean)) {
|
|
2539
|
-
diff = semver5.eq(c.currentClean, target) ? "patch" : diffVersions(c.currentClean, target);
|
|
2540
|
-
}
|
|
3866
|
+
const policy = strategy.versioning;
|
|
3867
|
+
const diff = c.currentClean ? policy.diff(c.currentClean, chosenVersion) : "minor";
|
|
2541
3868
|
const isPre = isPreReleaseVersion(chosenVersion);
|
|
2542
3869
|
return {
|
|
2543
3870
|
...c,
|
|
2544
|
-
newRange:
|
|
3871
|
+
newRange: policy.rewrite(c.currentRange, chosenVersion),
|
|
2545
3872
|
latest: chosenVersion,
|
|
2546
3873
|
diff,
|
|
2547
3874
|
selected: true,
|
|
@@ -2551,7 +3878,7 @@ function usePackages(strategy, module, modules, config, channel = "stable") {
|
|
|
2551
3878
|
};
|
|
2552
3879
|
})
|
|
2553
3880
|
);
|
|
2554
|
-
}, []);
|
|
3881
|
+
}, [strategy]);
|
|
2555
3882
|
const switchTab = useCallback3((tab) => {
|
|
2556
3883
|
setActiveTab(tab);
|
|
2557
3884
|
setCursor(0);
|
|
@@ -2583,24 +3910,24 @@ function usePackages(strategy, module, modules, config, channel = "stable") {
|
|
|
2583
3910
|
import { useState as useState4, useCallback as useCallback4 } from "react";
|
|
2584
3911
|
|
|
2585
3912
|
// src/application/update-workflow.ts
|
|
2586
|
-
import { existsSync as
|
|
2587
|
-
import { relative as
|
|
3913
|
+
import { existsSync as existsSync18 } from "fs";
|
|
3914
|
+
import { relative as relative10 } from "path";
|
|
2588
3915
|
|
|
2589
3916
|
// src/adapters/shared/file-snapshot.ts
|
|
2590
|
-
import { existsSync as
|
|
3917
|
+
import { existsSync as existsSync17, readFileSync as readFileSync17, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
2591
3918
|
var FileSnapshot = class _FileSnapshot {
|
|
2592
|
-
constructor(
|
|
2593
|
-
this.entries =
|
|
3919
|
+
constructor(entries2) {
|
|
3920
|
+
this.entries = entries2;
|
|
2594
3921
|
}
|
|
2595
3922
|
entries;
|
|
2596
3923
|
/** Records the current content (or absence) of each file. */
|
|
2597
3924
|
static capture(files) {
|
|
2598
|
-
const
|
|
3925
|
+
const entries2 = /* @__PURE__ */ new Map();
|
|
2599
3926
|
for (const file of files) {
|
|
2600
|
-
if (
|
|
2601
|
-
|
|
3927
|
+
if (entries2.has(file)) continue;
|
|
3928
|
+
entries2.set(file, existsSync17(file) ? readFileSync17(file) : null);
|
|
2602
3929
|
}
|
|
2603
|
-
return new _FileSnapshot(
|
|
3930
|
+
return new _FileSnapshot(entries2);
|
|
2604
3931
|
}
|
|
2605
3932
|
/** The files covered by this snapshot. */
|
|
2606
3933
|
get files() {
|
|
@@ -2610,9 +3937,9 @@ var FileSnapshot = class _FileSnapshot {
|
|
|
2610
3937
|
changedFiles() {
|
|
2611
3938
|
return this.files.filter((file) => {
|
|
2612
3939
|
const before = this.entries.get(file) ?? null;
|
|
2613
|
-
const exists =
|
|
3940
|
+
const exists = existsSync17(file);
|
|
2614
3941
|
if (before === null) return exists;
|
|
2615
|
-
return !exists || !
|
|
3942
|
+
return !exists || !readFileSync17(file).equals(before);
|
|
2616
3943
|
});
|
|
2617
3944
|
}
|
|
2618
3945
|
/** Writes every file back to its captured state; removes files that did not exist. */
|
|
@@ -2621,7 +3948,7 @@ var FileSnapshot = class _FileSnapshot {
|
|
|
2621
3948
|
if (content === null) {
|
|
2622
3949
|
rmSync2(file, { force: true });
|
|
2623
3950
|
} else {
|
|
2624
|
-
|
|
3951
|
+
writeFileSync5(file, content);
|
|
2625
3952
|
}
|
|
2626
3953
|
}
|
|
2627
3954
|
}
|
|
@@ -2660,7 +3987,7 @@ Restoring the installed state failed as well: ${message(err)}`;
|
|
|
2660
3987
|
let baseline;
|
|
2661
3988
|
if (!options.skipVerification) {
|
|
2662
3989
|
progress("Running verification on the current state\u2026");
|
|
2663
|
-
baseline = await verify(snapshot.files.filter((file) =>
|
|
3990
|
+
baseline = await verify(snapshot.files.filter((file) => existsSync18(file)));
|
|
2664
3991
|
}
|
|
2665
3992
|
let applied;
|
|
2666
3993
|
try {
|
|
@@ -2706,7 +4033,7 @@ function message(err) {
|
|
|
2706
4033
|
return err instanceof Error ? err.message : String(err);
|
|
2707
4034
|
}
|
|
2708
4035
|
function displayPaths(files, rootDir) {
|
|
2709
|
-
return files.map((file) =>
|
|
4036
|
+
return files.map((file) => relative10(rootDir, file).replace(/\\/g, "/"));
|
|
2710
4037
|
}
|
|
2711
4038
|
|
|
2712
4039
|
// src/hooks/use-updater.ts
|
|
@@ -2864,7 +4191,8 @@ var Header = ({
|
|
|
2864
4191
|
currentLocale,
|
|
2865
4192
|
strategy,
|
|
2866
4193
|
nextStrategyName,
|
|
2867
|
-
channel
|
|
4194
|
+
channel,
|
|
4195
|
+
rootDir
|
|
2868
4196
|
}) => /* @__PURE__ */ jsxs2(
|
|
2869
4197
|
Box2,
|
|
2870
4198
|
{
|
|
@@ -2885,7 +4213,7 @@ var Header = ({
|
|
|
2885
4213
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.colors.brandLight, bold: true, children: [
|
|
2886
4214
|
strategy.icon,
|
|
2887
4215
|
" ",
|
|
2888
|
-
strategy.displayName
|
|
4216
|
+
rootDir && strategy.displayNameFor?.(rootDir) || strategy.displayName
|
|
2889
4217
|
] }),
|
|
2890
4218
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.colors.muted, children: [
|
|
2891
4219
|
" [",
|
|
@@ -2919,33 +4247,39 @@ var Header = ({
|
|
|
2919
4247
|
// src/components/workspace-bar.tsx
|
|
2920
4248
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
2921
4249
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2922
|
-
var WorkspaceBar = ({ module, internalScopes, strategy, t }) => {
|
|
4250
|
+
var WorkspaceBar = ({ module, internalScopes, strategy, t, diagnostics = [] }) => {
|
|
2923
4251
|
const scopeHint = internalScopes.length > 0 ? t.status.internalScopes(internalScopes) : t.status.noInternalScopes;
|
|
2924
|
-
return /* @__PURE__ */ jsxs3(Box3, {
|
|
2925
|
-
/* @__PURE__ */ jsxs3(Box3, {
|
|
2926
|
-
/* @__PURE__ */ jsxs3(
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
4252
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
4253
|
+
/* @__PURE__ */ jsxs3(Box3, { marginY: 1, justifyContent: "space-between", alignItems: "center", children: [
|
|
4254
|
+
/* @__PURE__ */ jsxs3(Box3, { alignItems: "center", flexShrink: 1, children: [
|
|
4255
|
+
/* @__PURE__ */ jsxs3(Text3, { bold: true, children: [
|
|
4256
|
+
"\u{1F4CD} ",
|
|
4257
|
+
strategy.icon,
|
|
4258
|
+
" ",
|
|
4259
|
+
t.workspace.label,
|
|
4260
|
+
": "
|
|
4261
|
+
] }),
|
|
4262
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.colors.brandLight, children: module.name }),
|
|
4263
|
+
/* @__PURE__ */ jsxs3(Text3, { color: theme.colors.muted, children: [
|
|
4264
|
+
" (",
|
|
4265
|
+
module.relPath,
|
|
4266
|
+
")"
|
|
4267
|
+
] })
|
|
2932
4268
|
] }),
|
|
2933
|
-
/* @__PURE__ */
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
4269
|
+
/* @__PURE__ */ jsxs3(Box3, { alignItems: "center", marginLeft: 2, children: [
|
|
4270
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.colors.muted, children: scopeHint }),
|
|
4271
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.colors.muted, children: " \u2502 " }),
|
|
4272
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.colors.brandLight, bold: true, children: "[W]" }),
|
|
4273
|
+
/* @__PURE__ */ jsxs3(Text3, { color: theme.colors.muted, children: [
|
|
4274
|
+
" ",
|
|
4275
|
+
t.workspace.switchHint.replace("[W]", "").trim()
|
|
4276
|
+
] })
|
|
2938
4277
|
] })
|
|
2939
4278
|
] }),
|
|
2940
|
-
/* @__PURE__ */ jsxs3(
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
/* @__PURE__ */ jsxs3(Text3, { color: theme.colors.muted, children: [
|
|
2945
|
-
" ",
|
|
2946
|
-
t.workspace.switchHint.replace("[W]", "").trim()
|
|
2947
|
-
] })
|
|
2948
|
-
] })
|
|
4279
|
+
diagnostics.slice(0, 2).map((d) => /* @__PURE__ */ jsxs3(Text3, { color: theme.colors.danger, wrap: "truncate-end", children: [
|
|
4280
|
+
"\u26A0 ",
|
|
4281
|
+
d.split("\n")[0]
|
|
4282
|
+
] }, d))
|
|
2949
4283
|
] });
|
|
2950
4284
|
};
|
|
2951
4285
|
|
|
@@ -2995,7 +4329,9 @@ import { Box as Box5, Text as Text5 } from "ink";
|
|
|
2995
4329
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2996
4330
|
var PackageRow = ({ item, isFocused, t }) => {
|
|
2997
4331
|
const badge = item.diff === "major" ? /* @__PURE__ */ jsx5(Text5, { bold: true, color: theme.colors.danger, children: t.badges.major }) : item.diff === "minor" ? /* @__PURE__ */ jsx5(Text5, { color: theme.colors.warning, children: t.badges.minor }) : /* @__PURE__ */ jsx5(Text5, { color: theme.colors.success, children: t.badges.patch });
|
|
2998
|
-
const
|
|
4332
|
+
const shared = item.sharedWith && item.sharedWith.length > 0 ? ` ${t.badges.shared(item.sharedWith.length)}` : "";
|
|
4333
|
+
const label = item.qualifier ? `${item.coordinate.identifier} (${item.qualifier})` : item.coordinate.identifier;
|
|
4334
|
+
const displayName = label.slice(0, 42 - shared.length) + shared;
|
|
2999
4335
|
const isDevOrTest = item.scope === "dev" || item.scope === "test";
|
|
3000
4336
|
const property = item.declaration?.kind === "property" ? `\${${item.declaration.propertyName}}` : void 0;
|
|
3001
4337
|
return /* @__PURE__ */ jsxs5(Box5, { justifyContent: "space-between", children: [
|
|
@@ -3174,7 +4510,7 @@ var ChickenMascot = ({ state = "idle", t, ecosystem = "npm" }) => {
|
|
|
3174
4510
|
chickFace = cur.face;
|
|
3175
4511
|
wingLeft = cur.l;
|
|
3176
4512
|
wingRight = cur.r;
|
|
3177
|
-
const chirpsPool = ecosystem === "maven" ? [...t.mascot.idleChirps, ...t.mascot.mavenChirps] : t.mascot.idleChirps;
|
|
4513
|
+
const chirpsPool = ecosystem === "maven" ? [...t.mascot.idleChirps, ...t.mascot.mavenChirps] : ecosystem === "cargo" ? [...t.mascot.idleChirps, ...t.mascot.cargoChirps] : t.mascot.idleChirps;
|
|
3178
4514
|
currentChirp = chirpsPool[chirpIndex % chirpsPool.length];
|
|
3179
4515
|
}
|
|
3180
4516
|
return /* @__PURE__ */ jsxs7(Box7, { alignItems: "center", children: [
|
|
@@ -3452,6 +4788,7 @@ var VersionModal = ({
|
|
|
3452
4788
|
] }),
|
|
3453
4789
|
/* @__PURE__ */ jsx11(Text11, { color: theme.colors.muted, children: t.versionModal.current(candidate.currentRange) })
|
|
3454
4790
|
] }),
|
|
4791
|
+
candidate.sharedWith && candidate.sharedWith.length > 0 && /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text11, { color: theme.colors.warning, children: t.versionModal.alsoChanges(candidate.sharedWith) }) }),
|
|
3455
4792
|
/* @__PURE__ */ jsxs11(
|
|
3456
4793
|
Box11,
|
|
3457
4794
|
{
|
|
@@ -3626,10 +4963,10 @@ var App = ({ config }) => {
|
|
|
3626
4963
|
const [versionPickerCandidate, setVersionPickerCandidate] = useState8(null);
|
|
3627
4964
|
const [ecosystem, setEcosystem] = useState8("npm");
|
|
3628
4965
|
const strategy = useMemo4(() => EcosystemFactory.getStrategy(ecosystem), [ecosystem]);
|
|
3629
|
-
const nextEcosystem = ecosystem
|
|
4966
|
+
const nextEcosystem = EcosystemFactory.next(ecosystem);
|
|
3630
4967
|
const nextStrategy = useMemo4(() => EcosystemFactory.getStrategy(nextEcosystem), [nextEcosystem]);
|
|
3631
4968
|
const toggleEcosystem = useCallback5(() => {
|
|
3632
|
-
setEcosystem((prev) => prev
|
|
4969
|
+
setEcosystem((prev) => EcosystemFactory.next(prev));
|
|
3633
4970
|
}, []);
|
|
3634
4971
|
const { locale, toggleLocale, t } = useI18n(resolveInitialLocale(config.locale));
|
|
3635
4972
|
const ws = useWorkspaces(strategy, config.rootDir);
|
|
@@ -3733,7 +5070,8 @@ var App = ({ config }) => {
|
|
|
3733
5070
|
currentLocale: locale,
|
|
3734
5071
|
strategy,
|
|
3735
5072
|
nextStrategyName: `${nextStrategy.icon} ${nextStrategy.displayName}`,
|
|
3736
|
-
channel: config.channel
|
|
5073
|
+
channel: config.channel,
|
|
5074
|
+
rootDir: config.rootDir
|
|
3737
5075
|
}
|
|
3738
5076
|
),
|
|
3739
5077
|
view === "workspace_modal" && /* @__PURE__ */ jsx14(
|
|
@@ -3780,7 +5118,8 @@ var App = ({ config }) => {
|
|
|
3780
5118
|
module: ws.active,
|
|
3781
5119
|
internalScopes: config.internalScopes,
|
|
3782
5120
|
strategy,
|
|
3783
|
-
t
|
|
5121
|
+
t,
|
|
5122
|
+
diagnostics: ws.diagnostics
|
|
3784
5123
|
}
|
|
3785
5124
|
),
|
|
3786
5125
|
/* @__PURE__ */ jsx14(
|
|
@@ -3820,6 +5159,7 @@ var App = ({ config }) => {
|
|
|
3820
5159
|
};
|
|
3821
5160
|
|
|
3822
5161
|
// src/cli/parser.ts
|
|
5162
|
+
var isEcosystem = (v) => EcosystemFactory.getAvailableEcosystems().includes(v);
|
|
3823
5163
|
function parseCliArgs(args) {
|
|
3824
5164
|
const [first, ...rest] = args;
|
|
3825
5165
|
if (!first || first === "--help" || first === "-h" || first === "help") {
|
|
@@ -3857,10 +5197,10 @@ function parseCliArgs(args) {
|
|
|
3857
5197
|
result.help = true;
|
|
3858
5198
|
} else if (arg.startsWith("--ecosystem=")) {
|
|
3859
5199
|
const val = arg.split("=")[1];
|
|
3860
|
-
if (val
|
|
5200
|
+
if (isEcosystem(val)) result.ecosystem = val;
|
|
3861
5201
|
} else if (arg === "-e" || arg === "--ecosystem") {
|
|
3862
5202
|
const val = remaining[++i];
|
|
3863
|
-
if (val
|
|
5203
|
+
if (isEcosystem(val)) result.ecosystem = val;
|
|
3864
5204
|
} else if (arg.startsWith("--module=")) {
|
|
3865
5205
|
result.module = arg.split("=")[1];
|
|
3866
5206
|
} else if (arg === "-m" || arg === "--module") {
|
|
@@ -3902,7 +5242,7 @@ SUBCOMMANDS:
|
|
|
3902
5242
|
mcp Start stdio-based MCP server
|
|
3903
5243
|
|
|
3904
5244
|
FLAGS:
|
|
3905
|
-
-e, --ecosystem <npm|maven> Target ecosystem (default: npm)
|
|
5245
|
+
-e, --ecosystem <npm|maven|cargo> Target ecosystem (default: npm)
|
|
3906
5246
|
-m, --module <name|path> Target module ID or relative path (default: root)
|
|
3907
5247
|
-c, --channel <stable|all> Release channel (default: stable)
|
|
3908
5248
|
-p, --packages <pkg1,pkg2@1.2.3> Comma-separated list of packages to update
|
|
@@ -3930,19 +5270,22 @@ EXAMPLES:
|
|
|
3930
5270
|
async function handleModulesCommand(options, config) {
|
|
3931
5271
|
const ecosystem = options.ecosystem ?? "npm";
|
|
3932
5272
|
const strategy = EcosystemFactory.getStrategy(ecosystem);
|
|
3933
|
-
const modules = await strategy
|
|
5273
|
+
const { modules, diagnostics } = await discoverModules(strategy, config.rootDir);
|
|
5274
|
+
const displayName = strategy.displayNameFor?.(config.rootDir) ?? strategy.displayName;
|
|
5275
|
+
const packageManager = strategy.packageManagerFor?.(config.rootDir);
|
|
3934
5276
|
if (options.json) {
|
|
3935
|
-
console.log(JSON.stringify({ ecosystem, count: modules.length, modules }, null, 2));
|
|
5277
|
+
console.log(JSON.stringify({ ecosystem, ...packageManager ? { packageManager } : {}, count: modules.length, modules, diagnostics }, null, 2));
|
|
3936
5278
|
return 0;
|
|
3937
5279
|
}
|
|
3938
5280
|
console.log(`
|
|
3939
|
-
\u{1FAB6} Discovered ${strategy.icon} ${
|
|
5281
|
+
\u{1FAB6} Discovered ${strategy.icon} ${displayName} Modules (${modules.length}):
|
|
3940
5282
|
`);
|
|
3941
5283
|
for (const m of modules) {
|
|
3942
5284
|
const isRootBadge = m.isRoot ? " [ROOT]" : "";
|
|
3943
5285
|
console.log(` \u2022 ${m.name.padEnd(45)} (${m.relPath})${isRootBadge}`);
|
|
3944
5286
|
}
|
|
3945
5287
|
console.log("");
|
|
5288
|
+
for (const problem of diagnostics) console.error(`\u26A0\uFE0F ${problem}`);
|
|
3946
5289
|
return 0;
|
|
3947
5290
|
}
|
|
3948
5291
|
|
|
@@ -3960,6 +5303,12 @@ function describeSkip(skipped) {
|
|
|
3960
5303
|
return `version location is ambiguous${detail}`;
|
|
3961
5304
|
case "invalid-name":
|
|
3962
5305
|
return "not a valid package name";
|
|
5306
|
+
case "not-linked":
|
|
5307
|
+
return `workspace package that pnpm does not link unless it is declared with the workspace: protocol${detail}`;
|
|
5308
|
+
case "catalog-unresolved":
|
|
5309
|
+
return `catalog entry could not be resolved${detail}`;
|
|
5310
|
+
case "incompatible-rust-version":
|
|
5311
|
+
return `newer versions need a newer Rust than the project's rust-version${detail}`;
|
|
3963
5312
|
}
|
|
3964
5313
|
}
|
|
3965
5314
|
|
|
@@ -3968,7 +5317,8 @@ async function handleScanCommand(options, config) {
|
|
|
3968
5317
|
const ecosystem = options.ecosystem ?? "npm";
|
|
3969
5318
|
const channel = options.channel ?? config.channel ?? "stable";
|
|
3970
5319
|
const strategy = EcosystemFactory.getStrategy(ecosystem);
|
|
3971
|
-
const modules = await strategy
|
|
5320
|
+
const { modules, diagnostics } = await discoverModules(strategy, config.rootDir);
|
|
5321
|
+
for (const problem of diagnostics) console.error(`\u26A0\uFE0F ${problem}`);
|
|
3972
5322
|
if (modules.length === 0) {
|
|
3973
5323
|
if (options.json) {
|
|
3974
5324
|
console.log(JSON.stringify({ error: `No ${strategy.displayName} modules found.`, ecosystem }, null, 2));
|
|
@@ -4118,7 +5468,7 @@ async function handleVersionsCommand(options, config) {
|
|
|
4118
5468
|
}
|
|
4119
5469
|
|
|
4120
5470
|
// src/application/update-selection.ts
|
|
4121
|
-
import
|
|
5471
|
+
import semver12 from "semver";
|
|
4122
5472
|
var UpdateSelectionError = class extends Error {
|
|
4123
5473
|
constructor(message2) {
|
|
4124
5474
|
super(message2);
|
|
@@ -4143,9 +5493,9 @@ function selectRequested(scan, requests, options) {
|
|
|
4143
5493
|
`'${request.identifier}' has no pending update. Specify a target version (e.g. ${request.identifier}@1.2.3).`
|
|
4144
5494
|
);
|
|
4145
5495
|
}
|
|
4146
|
-
if (options.
|
|
5496
|
+
if (!policyOf(options).allowsManualTargets) {
|
|
4147
5497
|
throw new UpdateSelectionError(
|
|
4148
|
-
`'${request.identifier}' has no pending update.
|
|
5498
|
+
`'${request.identifier}' has no pending update. Updates are written where the scan located the version; a dependency without an offered update cannot be retargeted.`
|
|
4149
5499
|
);
|
|
4150
5500
|
}
|
|
4151
5501
|
if (options.originOf(request.identifier) !== "public") {
|
|
@@ -4170,12 +5520,12 @@ function retarget(found, target, options) {
|
|
|
4170
5520
|
`'${found.coordinate.identifier}' is a workspace module; it can only be aligned to its local version ${found.latest}.`
|
|
4171
5521
|
);
|
|
4172
5522
|
}
|
|
4173
|
-
if (options.
|
|
5523
|
+
if (!policyOf(options).allowsManualTargets && found.availableVersions && !found.availableVersions.includes(target)) {
|
|
4174
5524
|
throw new UpdateSelectionError(
|
|
4175
5525
|
`'${found.coordinate.identifier}' has no version ${target} in the configured repositories. Available: ${found.availableVersions.slice(0, 5).join(", ")}${found.availableVersions.length > 5 ? ", \u2026" : ""}.`
|
|
4176
5526
|
);
|
|
4177
5527
|
}
|
|
4178
|
-
const diff =
|
|
5528
|
+
const diff = policyOf(options).diff(found.currentClean, target);
|
|
4179
5529
|
if (diff === "major" && !options.allowMajor) {
|
|
4180
5530
|
throw new UpdateSelectionError(
|
|
4181
5531
|
`'${found.coordinate.identifier}' requires a major upgrade to ${target}. Pass --allow-major / allowMajor.`
|
|
@@ -4184,7 +5534,7 @@ function retarget(found, target, options) {
|
|
|
4184
5534
|
const isPreRelease = isPreReleaseVersion(target);
|
|
4185
5535
|
return {
|
|
4186
5536
|
...found,
|
|
4187
|
-
newRange: options.
|
|
5537
|
+
newRange: policyOf(options).rewrite(found.currentRange, target),
|
|
4188
5538
|
diff,
|
|
4189
5539
|
selected: true,
|
|
4190
5540
|
isCustomVersion: true,
|
|
@@ -4193,7 +5543,7 @@ function retarget(found, target, options) {
|
|
|
4193
5543
|
};
|
|
4194
5544
|
}
|
|
4195
5545
|
function manualNpmCandidate(identifier, target, options) {
|
|
4196
|
-
if (!
|
|
5546
|
+
if (!semver12.valid(target)) {
|
|
4197
5547
|
throw new UpdateSelectionError(`'${target}' is not a valid version for '${identifier}'.`);
|
|
4198
5548
|
}
|
|
4199
5549
|
return {
|
|
@@ -4210,10 +5560,8 @@ function manualNpmCandidate(identifier, target, options) {
|
|
|
4210
5560
|
isCustomVersion: true
|
|
4211
5561
|
};
|
|
4212
5562
|
}
|
|
4213
|
-
function
|
|
4214
|
-
|
|
4215
|
-
if (!to || !semver6.valid(current)) return "minor";
|
|
4216
|
-
return semver6.eq(current, to) ? "patch" : diffVersions(current, to);
|
|
5563
|
+
function policyOf(options) {
|
|
5564
|
+
return EcosystemFactory.getStrategy(options.ecosystem).versioning;
|
|
4217
5565
|
}
|
|
4218
5566
|
|
|
4219
5567
|
// src/cli/command-update.ts
|
|
@@ -4225,7 +5573,8 @@ async function handleUpdateCommand(options, config) {
|
|
|
4225
5573
|
else console.error(`\u274C ${message2}`);
|
|
4226
5574
|
return code;
|
|
4227
5575
|
};
|
|
4228
|
-
const modules = await strategy
|
|
5576
|
+
const { modules, diagnostics } = await discoverModules(strategy, config.rootDir);
|
|
5577
|
+
for (const problem of diagnostics) console.error(`\u26A0\uFE0F ${problem}`);
|
|
4229
5578
|
if (modules.length === 0) return fail(`No ${strategy.displayName} modules found.`);
|
|
4230
5579
|
const targetModule = options.module ? findModule(modules, options.module) : modules[0];
|
|
4231
5580
|
if (!targetModule) return fail(`Module '${options.module}' not found.`);
|
|
@@ -4366,9 +5715,9 @@ function parsePackageArgument(item) {
|
|
|
4366
5715
|
}
|
|
4367
5716
|
|
|
4368
5717
|
// src/cli/command-init.ts
|
|
4369
|
-
import { existsSync as
|
|
4370
|
-
import { homedir, platform } from "os";
|
|
4371
|
-
import { join as
|
|
5718
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18, writeFileSync as writeFileSync6, mkdirSync as mkdirSync2 } from "fs";
|
|
5719
|
+
import { homedir as homedir2, platform } from "os";
|
|
5720
|
+
import { join as join24, resolve as resolve15 } from "path";
|
|
4372
5721
|
function mcpServerEntry() {
|
|
4373
5722
|
return {
|
|
4374
5723
|
command: "npx",
|
|
@@ -4411,10 +5760,10 @@ automatic rollback.
|
|
|
4411
5760
|
requested). A failed install or verification is rolled back automatically.
|
|
4412
5761
|
4. \`elevate_verify\` \u2014 Verify build integrity when needed
|
|
4413
5762
|
`;
|
|
4414
|
-
var home =
|
|
5763
|
+
var home = homedir2();
|
|
4415
5764
|
var isWin = platform() === "win32";
|
|
4416
5765
|
function appData() {
|
|
4417
|
-
return process.env.APPDATA ||
|
|
5766
|
+
return process.env.APPDATA || join24(home, "AppData", "Roaming");
|
|
4418
5767
|
}
|
|
4419
5768
|
function detectCursor(cwd) {
|
|
4420
5769
|
const env = {
|
|
@@ -4426,25 +5775,25 @@ function detectCursor(cwd) {
|
|
|
4426
5775
|
mcpConfigured: false,
|
|
4427
5776
|
skillConfigured: false
|
|
4428
5777
|
};
|
|
4429
|
-
const workspaceCursorDir =
|
|
4430
|
-
const globalCursorDir =
|
|
4431
|
-
if (
|
|
5778
|
+
const workspaceCursorDir = join24(cwd, ".cursor");
|
|
5779
|
+
const globalCursorDir = join24(home, ".cursor");
|
|
5780
|
+
if (existsSync19(workspaceCursorDir) || existsSync19(globalCursorDir)) {
|
|
4432
5781
|
env.detected = true;
|
|
4433
|
-
env.reason =
|
|
5782
|
+
env.reason = existsSync19(workspaceCursorDir) ? `Found .cursor/ in workspace` : `Found ~/.cursor/ global config`;
|
|
4434
5783
|
}
|
|
4435
|
-
const mcpPath =
|
|
4436
|
-
if (
|
|
5784
|
+
const mcpPath = join24(cwd, ".cursor", "mcp.json");
|
|
5785
|
+
if (existsSync19(mcpPath)) {
|
|
4437
5786
|
try {
|
|
4438
|
-
const content = JSON.parse(
|
|
5787
|
+
const content = JSON.parse(readFileSync18(mcpPath, "utf8"));
|
|
4439
5788
|
if (content?.mcpServers?.elevate) {
|
|
4440
5789
|
env.mcpConfigured = true;
|
|
4441
5790
|
}
|
|
4442
5791
|
} catch {
|
|
4443
5792
|
}
|
|
4444
5793
|
}
|
|
4445
|
-
const rulesPath =
|
|
4446
|
-
if (
|
|
4447
|
-
const content =
|
|
5794
|
+
const rulesPath = join24(cwd, ".cursorrules");
|
|
5795
|
+
if (existsSync19(rulesPath)) {
|
|
5796
|
+
const content = readFileSync18(rulesPath, "utf8");
|
|
4448
5797
|
if (content.includes("elevate") || content.includes("Elevate")) {
|
|
4449
5798
|
env.skillConfigured = true;
|
|
4450
5799
|
}
|
|
@@ -4461,12 +5810,12 @@ function detectClaudeDesktop() {
|
|
|
4461
5810
|
mcpConfigured: false,
|
|
4462
5811
|
skillConfigured: false
|
|
4463
5812
|
};
|
|
4464
|
-
const configPath = isWin ?
|
|
4465
|
-
if (
|
|
5813
|
+
const configPath = isWin ? join24(appData(), "Claude", "claude_desktop_config.json") : join24(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
5814
|
+
if (existsSync19(configPath)) {
|
|
4466
5815
|
env.detected = true;
|
|
4467
5816
|
env.reason = `Found ${configPath}`;
|
|
4468
5817
|
try {
|
|
4469
|
-
const content = JSON.parse(
|
|
5818
|
+
const content = JSON.parse(readFileSync18(configPath, "utf8"));
|
|
4470
5819
|
if (content?.mcpServers?.elevate) {
|
|
4471
5820
|
env.mcpConfigured = true;
|
|
4472
5821
|
}
|
|
@@ -4485,23 +5834,23 @@ function detectClaudeCode(cwd) {
|
|
|
4485
5834
|
mcpConfigured: false,
|
|
4486
5835
|
skillConfigured: false
|
|
4487
5836
|
};
|
|
4488
|
-
const mcpJsonPath =
|
|
4489
|
-
const claudeMdPath =
|
|
4490
|
-
if (
|
|
5837
|
+
const mcpJsonPath = join24(cwd, ".mcp.json");
|
|
5838
|
+
const claudeMdPath = join24(cwd, "CLAUDE.md");
|
|
5839
|
+
if (existsSync19(mcpJsonPath) || existsSync19(claudeMdPath)) {
|
|
4491
5840
|
env.detected = true;
|
|
4492
|
-
env.reason =
|
|
5841
|
+
env.reason = existsSync19(mcpJsonPath) ? `Found .mcp.json in workspace` : `Found CLAUDE.md in workspace`;
|
|
4493
5842
|
}
|
|
4494
|
-
if (
|
|
5843
|
+
if (existsSync19(mcpJsonPath)) {
|
|
4495
5844
|
try {
|
|
4496
|
-
const content = JSON.parse(
|
|
5845
|
+
const content = JSON.parse(readFileSync18(mcpJsonPath, "utf8"));
|
|
4497
5846
|
if (content?.mcpServers?.elevate) {
|
|
4498
5847
|
env.mcpConfigured = true;
|
|
4499
5848
|
}
|
|
4500
5849
|
} catch {
|
|
4501
5850
|
}
|
|
4502
5851
|
}
|
|
4503
|
-
if (
|
|
4504
|
-
const content =
|
|
5852
|
+
if (existsSync19(claudeMdPath)) {
|
|
5853
|
+
const content = readFileSync18(claudeMdPath, "utf8");
|
|
4505
5854
|
if (content.includes("elevate") || content.includes("Elevate")) {
|
|
4506
5855
|
env.skillConfigured = true;
|
|
4507
5856
|
}
|
|
@@ -4518,20 +5867,20 @@ function detectAntigravity(cwd) {
|
|
|
4518
5867
|
mcpConfigured: false,
|
|
4519
5868
|
skillConfigured: false
|
|
4520
5869
|
};
|
|
4521
|
-
const globalGeminiDir =
|
|
4522
|
-
const workspaceAgentsDir =
|
|
4523
|
-
if (
|
|
5870
|
+
const globalGeminiDir = join24(home, ".gemini");
|
|
5871
|
+
const workspaceAgentsDir = join24(cwd, ".agents");
|
|
5872
|
+
if (existsSync19(globalGeminiDir) || existsSync19(workspaceAgentsDir)) {
|
|
4524
5873
|
env.detected = true;
|
|
4525
|
-
env.reason =
|
|
5874
|
+
env.reason = existsSync19(workspaceAgentsDir) ? `Found .agents/ in workspace` : `Found ~/.gemini/ global config`;
|
|
4526
5875
|
}
|
|
4527
5876
|
const mcpPaths = [
|
|
4528
|
-
|
|
4529
|
-
|
|
5877
|
+
join24(cwd, ".agents", "mcp_config.json"),
|
|
5878
|
+
join24(home, ".gemini", "config", "mcp_config.json")
|
|
4530
5879
|
];
|
|
4531
5880
|
for (const p of mcpPaths) {
|
|
4532
|
-
if (
|
|
5881
|
+
if (existsSync19(p)) {
|
|
4533
5882
|
try {
|
|
4534
|
-
const content = JSON.parse(
|
|
5883
|
+
const content = JSON.parse(readFileSync18(p, "utf8"));
|
|
4535
5884
|
if (content?.mcpServers?.elevate) {
|
|
4536
5885
|
env.mcpConfigured = true;
|
|
4537
5886
|
break;
|
|
@@ -4552,24 +5901,24 @@ function detectVSCode(cwd) {
|
|
|
4552
5901
|
mcpConfigured: false,
|
|
4553
5902
|
skillConfigured: false
|
|
4554
5903
|
};
|
|
4555
|
-
const vscodeDir =
|
|
4556
|
-
if (
|
|
5904
|
+
const vscodeDir = join24(cwd, ".vscode");
|
|
5905
|
+
if (existsSync19(vscodeDir)) {
|
|
4557
5906
|
env.detected = true;
|
|
4558
5907
|
env.reason = `Found .vscode/ in workspace`;
|
|
4559
5908
|
}
|
|
4560
|
-
const mcpPath =
|
|
4561
|
-
if (
|
|
5909
|
+
const mcpPath = join24(cwd, ".vscode", "mcp.json");
|
|
5910
|
+
if (existsSync19(mcpPath)) {
|
|
4562
5911
|
try {
|
|
4563
|
-
const content = JSON.parse(
|
|
5912
|
+
const content = JSON.parse(readFileSync18(mcpPath, "utf8"));
|
|
4564
5913
|
if (content?.servers?.elevate) {
|
|
4565
5914
|
env.mcpConfigured = true;
|
|
4566
5915
|
}
|
|
4567
5916
|
} catch {
|
|
4568
5917
|
}
|
|
4569
5918
|
}
|
|
4570
|
-
const copilotInstructions =
|
|
4571
|
-
if (
|
|
4572
|
-
const content =
|
|
5919
|
+
const copilotInstructions = join24(cwd, ".github", "copilot-instructions.md");
|
|
5920
|
+
if (existsSync19(copilotInstructions)) {
|
|
5921
|
+
const content = readFileSync18(copilotInstructions, "utf8");
|
|
4573
5922
|
if (content.includes("elevate") || content.includes("Elevate")) {
|
|
4574
5923
|
env.skillConfigured = true;
|
|
4575
5924
|
}
|
|
@@ -4586,14 +5935,14 @@ function detectWindsurf() {
|
|
|
4586
5935
|
mcpConfigured: false,
|
|
4587
5936
|
skillConfigured: false
|
|
4588
5937
|
};
|
|
4589
|
-
const configPath = isWin ?
|
|
4590
|
-
const altPath =
|
|
4591
|
-
if (
|
|
5938
|
+
const configPath = isWin ? join24(appData(), "Codeium", "Windsurf", "mcp_config.json") : join24(home, ".codeium", "windsurf", "mcp_config.json");
|
|
5939
|
+
const altPath = join24(home, ".codeium", "windsurf", "mcp_config.json");
|
|
5940
|
+
if (existsSync19(configPath) || existsSync19(altPath)) {
|
|
4592
5941
|
env.detected = true;
|
|
4593
5942
|
env.reason = `Found Windsurf config`;
|
|
4594
|
-
const path =
|
|
5943
|
+
const path = existsSync19(configPath) ? configPath : altPath;
|
|
4595
5944
|
try {
|
|
4596
|
-
const content = JSON.parse(
|
|
5945
|
+
const content = JSON.parse(readFileSync18(path, "utf8"));
|
|
4597
5946
|
if (content?.mcpServers?.elevate) {
|
|
4598
5947
|
env.mcpConfigured = true;
|
|
4599
5948
|
}
|
|
@@ -4604,9 +5953,9 @@ function detectWindsurf() {
|
|
|
4604
5953
|
}
|
|
4605
5954
|
function mergeJsonMcpServer(filePath, serverKey, topLevelKey) {
|
|
4606
5955
|
let existing = {};
|
|
4607
|
-
if (
|
|
5956
|
+
if (existsSync19(filePath)) {
|
|
4608
5957
|
try {
|
|
4609
|
-
existing = JSON.parse(
|
|
5958
|
+
existing = JSON.parse(readFileSync18(filePath, "utf8"));
|
|
4610
5959
|
} catch {
|
|
4611
5960
|
existing = {};
|
|
4612
5961
|
}
|
|
@@ -4618,69 +5967,69 @@ function mergeJsonMcpServer(filePath, serverKey, topLevelKey) {
|
|
|
4618
5967
|
servers[serverKey] = mcpServerEntry();
|
|
4619
5968
|
existing[topLevelKey] = servers;
|
|
4620
5969
|
const dir = filePath.substring(0, filePath.lastIndexOf(isWin ? "\\" : "/"));
|
|
4621
|
-
if (dir && !
|
|
5970
|
+
if (dir && !existsSync19(dir)) {
|
|
4622
5971
|
mkdirSync2(dir, { recursive: true });
|
|
4623
5972
|
}
|
|
4624
|
-
|
|
5973
|
+
writeFileSync6(filePath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
4625
5974
|
return { written: true, message: `${filePath}: elevate MCP server registered` };
|
|
4626
5975
|
}
|
|
4627
5976
|
function appendSkillToFile(filePath, content, marker) {
|
|
4628
|
-
if (
|
|
4629
|
-
const existing =
|
|
5977
|
+
if (existsSync19(filePath)) {
|
|
5978
|
+
const existing = readFileSync18(filePath, "utf8");
|
|
4630
5979
|
if (existing.includes(marker)) {
|
|
4631
5980
|
return { written: false, message: `${filePath}: skill already present` };
|
|
4632
5981
|
}
|
|
4633
|
-
|
|
5982
|
+
writeFileSync6(filePath, existing + "\n\n" + content, "utf8");
|
|
4634
5983
|
return { written: true, message: `${filePath}: skill appended` };
|
|
4635
5984
|
}
|
|
4636
5985
|
const dir = filePath.substring(0, filePath.lastIndexOf(isWin ? "\\" : "/"));
|
|
4637
|
-
if (dir && !
|
|
5986
|
+
if (dir && !existsSync19(dir)) {
|
|
4638
5987
|
mkdirSync2(dir, { recursive: true });
|
|
4639
5988
|
}
|
|
4640
|
-
|
|
5989
|
+
writeFileSync6(filePath, content, "utf8");
|
|
4641
5990
|
return { written: true, message: `${filePath}: skill created` };
|
|
4642
5991
|
}
|
|
4643
5992
|
function configureCursor(cwd, result) {
|
|
4644
|
-
const mcpPath =
|
|
5993
|
+
const mcpPath = join24(cwd, ".cursor", "mcp.json");
|
|
4645
5994
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
4646
5995
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Cursor MCP: ${mcp.message}`);
|
|
4647
|
-
const rulesPath =
|
|
5996
|
+
const rulesPath = join24(cwd, ".cursorrules");
|
|
4648
5997
|
const skill = appendSkillToFile(rulesPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
|
|
4649
5998
|
result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Cursor Rules: ${skill.message}`);
|
|
4650
5999
|
}
|
|
4651
6000
|
function configureClaudeDesktop(result) {
|
|
4652
|
-
const configPath = isWin ?
|
|
6001
|
+
const configPath = isWin ? join24(appData(), "Claude", "claude_desktop_config.json") : join24(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
4653
6002
|
const mcp = mergeJsonMcpServer(configPath, "elevate", "mcpServers");
|
|
4654
6003
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Claude Desktop MCP: ${mcp.message}`);
|
|
4655
6004
|
}
|
|
4656
6005
|
function configureClaudeCode(cwd, result) {
|
|
4657
|
-
const mcpPath =
|
|
6006
|
+
const mcpPath = join24(cwd, ".mcp.json");
|
|
4658
6007
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
4659
6008
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Claude Code MCP: ${mcp.message}`);
|
|
4660
|
-
const claudeMdPath =
|
|
6009
|
+
const claudeMdPath = join24(cwd, "CLAUDE.md");
|
|
4661
6010
|
const skill = appendSkillToFile(claudeMdPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
|
|
4662
6011
|
result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Claude Code Skill: ${skill.message}`);
|
|
4663
6012
|
}
|
|
4664
6013
|
function configureAntigravity(cwd, result) {
|
|
4665
|
-
const mcpPath =
|
|
6014
|
+
const mcpPath = join24(cwd, ".agents", "mcp_config.json");
|
|
4666
6015
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
4667
6016
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Antigravity MCP: ${mcp.message}`);
|
|
4668
6017
|
}
|
|
4669
6018
|
function configureVSCode(cwd, result) {
|
|
4670
|
-
const mcpPath =
|
|
6019
|
+
const mcpPath = join24(cwd, ".vscode", "mcp.json");
|
|
4671
6020
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "servers");
|
|
4672
6021
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} VS Code MCP: ${mcp.message}`);
|
|
4673
|
-
const instructionsPath =
|
|
6022
|
+
const instructionsPath = join24(cwd, ".github", "copilot-instructions.md");
|
|
4674
6023
|
const skill = appendSkillToFile(instructionsPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
|
|
4675
6024
|
result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Copilot Instructions: ${skill.message}`);
|
|
4676
6025
|
}
|
|
4677
6026
|
function configureWindsurf(result) {
|
|
4678
|
-
const configPath = isWin ?
|
|
6027
|
+
const configPath = isWin ? join24(appData(), "Codeium", "Windsurf", "mcp_config.json") : join24(home, ".codeium", "windsurf", "mcp_config.json");
|
|
4679
6028
|
const mcp = mergeJsonMcpServer(configPath, "elevate", "mcpServers");
|
|
4680
6029
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Windsurf MCP: ${mcp.message}`);
|
|
4681
6030
|
}
|
|
4682
6031
|
async function handleInitCommand(options) {
|
|
4683
|
-
const cwd =
|
|
6032
|
+
const cwd = resolve15(process.cwd());
|
|
4684
6033
|
const result = {
|
|
4685
6034
|
cwd,
|
|
4686
6035
|
environments: [],
|
|
@@ -4708,7 +6057,7 @@ async function handleInitCommand(options) {
|
|
|
4708
6057
|
console.log(` Creating portable .mcp.json for broad compatibility.
|
|
4709
6058
|
`);
|
|
4710
6059
|
}
|
|
4711
|
-
const mcpPath =
|
|
6060
|
+
const mcpPath = join24(cwd, ".mcp.json");
|
|
4712
6061
|
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
4713
6062
|
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Generic MCP: ${mcp.message}`);
|
|
4714
6063
|
} else {
|
|
@@ -4775,14 +6124,14 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4775
6124
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4776
6125
|
var ECOSYSTEM_PROPERTY = {
|
|
4777
6126
|
type: "string",
|
|
4778
|
-
enum:
|
|
6127
|
+
enum: EcosystemFactory.getAvailableEcosystems(),
|
|
4779
6128
|
description: "Target ecosystem (default: npm)"
|
|
4780
6129
|
};
|
|
4781
6130
|
async function startMcpServer(config) {
|
|
4782
6131
|
const server = new Server(
|
|
4783
6132
|
{
|
|
4784
6133
|
name: "elevate-mcp",
|
|
4785
|
-
version:
|
|
6134
|
+
version: VERSION
|
|
4786
6135
|
},
|
|
4787
6136
|
{
|
|
4788
6137
|
capabilities: {
|
|
@@ -4795,7 +6144,7 @@ async function startMcpServer(config) {
|
|
|
4795
6144
|
tools: [
|
|
4796
6145
|
{
|
|
4797
6146
|
name: "elevate_discover_modules",
|
|
4798
|
-
description: "Discovers all repository modules and workspaces for Node/npm (package.json)
|
|
6147
|
+
description: "Discovers all repository modules and workspaces for Node/npm (package.json), Java/Maven (pom.xml) or Rust/Cargo (Cargo.toml).",
|
|
4799
6148
|
inputSchema: {
|
|
4800
6149
|
type: "object",
|
|
4801
6150
|
properties: {
|
|
@@ -4835,7 +6184,7 @@ async function startMcpServer(config) {
|
|
|
4835
6184
|
ecosystem: ECOSYSTEM_PROPERTY,
|
|
4836
6185
|
identifier: {
|
|
4837
6186
|
type: "string",
|
|
4838
|
-
description: "Package identifier (npm: chalk, maven: org.slf4j:slf4j-api)"
|
|
6187
|
+
description: "Package identifier (npm: chalk, maven: org.slf4j:slf4j-api, cargo: serde)"
|
|
4839
6188
|
}
|
|
4840
6189
|
},
|
|
4841
6190
|
required: ["identifier"]
|
|
@@ -4916,10 +6265,13 @@ async function startMcpServer(config) {
|
|
|
4916
6265
|
};
|
|
4917
6266
|
try {
|
|
4918
6267
|
if (name === "elevate_discover_modules") {
|
|
4919
|
-
const modules = await strategy
|
|
6268
|
+
const { modules, diagnostics } = await discoverModules(strategy, config.rootDir);
|
|
6269
|
+
const packageManager = strategy.packageManagerFor?.(config.rootDir);
|
|
4920
6270
|
return reply({
|
|
4921
6271
|
ecosystem,
|
|
6272
|
+
...packageManager ? { packageManager } : {},
|
|
4922
6273
|
count: modules.length,
|
|
6274
|
+
diagnostics,
|
|
4923
6275
|
modules: modules.map((m) => ({
|
|
4924
6276
|
id: m.id,
|
|
4925
6277
|
name: m.name,
|