@roblourens/dap-cli 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -8
- package/dist/index.js +1779 -183
- package/dist/index.js.map +1 -1
- package/package.json +10 -3
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ var CliError = class extends Error {
|
|
|
11
11
|
adapter;
|
|
12
12
|
data;
|
|
13
13
|
constructor(message, category, exitCode, options = {}) {
|
|
14
|
-
super(message);
|
|
14
|
+
super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
15
15
|
this.name = "CliError";
|
|
16
16
|
this.code = options.code ?? `${category}_error`;
|
|
17
17
|
this.category = category;
|
|
@@ -846,8 +846,10 @@ function breakpointBindingGuidance(options = {}) {
|
|
|
846
846
|
}
|
|
847
847
|
|
|
848
848
|
// src/controller/ipc.ts
|
|
849
|
+
import { createHash } from "crypto";
|
|
849
850
|
import { promises as fs } from "fs";
|
|
850
851
|
import net from "net";
|
|
852
|
+
import { tmpdir } from "os";
|
|
851
853
|
import path2 from "path";
|
|
852
854
|
import { z as z2 } from "zod";
|
|
853
855
|
|
|
@@ -869,6 +871,10 @@ function getDapCliLogDir(env = process.env) {
|
|
|
869
871
|
return path.join(getDapCliHome(env), "logs");
|
|
870
872
|
}
|
|
871
873
|
function getDapCliAdaptersDir(env = process.env) {
|
|
874
|
+
const configured = env.DAP_CLI_ADAPTERS_DIR;
|
|
875
|
+
if (configured !== void 0 && configured.trim().length > 0) {
|
|
876
|
+
return path.resolve(configured);
|
|
877
|
+
}
|
|
872
878
|
return path.join(getDapCliHome(env), "adapters");
|
|
873
879
|
}
|
|
874
880
|
function getDapCliVenvPythonPath(env = process.env) {
|
|
@@ -897,6 +903,7 @@ var controllerDiscoverySchema = z2.object({
|
|
|
897
903
|
startedAt: z2.string().min(1),
|
|
898
904
|
lastHeartbeatAt: z2.string().min(1)
|
|
899
905
|
});
|
|
906
|
+
var maximumPortableUnixSocketPathLength = 100;
|
|
900
907
|
function resolveControllerDiscoveryPath(options = {}) {
|
|
901
908
|
return path2.join(resolveStateDir(options), "controller.json");
|
|
902
909
|
}
|
|
@@ -978,7 +985,12 @@ function createControllerEndpoint(stateDir, platform) {
|
|
|
978
985
|
if (platform === "win32") {
|
|
979
986
|
return { kind: "ipc", path: `\\\\.\\pipe\\dap-cli-${Buffer.from(stateDir).toString("hex").slice(0, 24)}` };
|
|
980
987
|
}
|
|
981
|
-
|
|
988
|
+
const endpointPath = path2.join(stateDir, "controller.sock");
|
|
989
|
+
if (Buffer.byteLength(endpointPath, "utf8") <= maximumPortableUnixSocketPathLength) {
|
|
990
|
+
return { kind: "ipc", path: endpointPath };
|
|
991
|
+
}
|
|
992
|
+
const stateHash = createHash("sha256").update(stateDir).digest("hex").slice(0, 24);
|
|
993
|
+
return { kind: "ipc", path: path2.join(tmpdir(), `dap-cli-${stateHash}.sock`) };
|
|
982
994
|
}
|
|
983
995
|
function resolveStateDir(options) {
|
|
984
996
|
return getDapCliStateDir(createEnv(options));
|
|
@@ -1153,8 +1165,8 @@ function isSessionErrorCode(code) {
|
|
|
1153
1165
|
}
|
|
1154
1166
|
|
|
1155
1167
|
// src/controller/server.ts
|
|
1156
|
-
import
|
|
1157
|
-
import { randomBytes as
|
|
1168
|
+
import path17 from "path";
|
|
1169
|
+
import { randomBytes as randomBytes6 } from "crypto";
|
|
1158
1170
|
|
|
1159
1171
|
// src/adapters/descriptor.ts
|
|
1160
1172
|
import { z as z3 } from "zod";
|
|
@@ -1306,9 +1318,16 @@ function createSocketTransport(options) {
|
|
|
1306
1318
|
readable: options.socket,
|
|
1307
1319
|
writable: options.socket,
|
|
1308
1320
|
close() {
|
|
1309
|
-
options.socket.
|
|
1310
|
-
|
|
1311
|
-
|
|
1321
|
+
if (options.socket.closed) {
|
|
1322
|
+
return Promise.resolve();
|
|
1323
|
+
}
|
|
1324
|
+
return new Promise((resolve) => {
|
|
1325
|
+
options.socket.once("close", resolve);
|
|
1326
|
+
if (!options.socket.destroyed) {
|
|
1327
|
+
options.socket.end();
|
|
1328
|
+
options.socket.destroy();
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1312
1331
|
}
|
|
1313
1332
|
};
|
|
1314
1333
|
}
|
|
@@ -1342,7 +1361,7 @@ async function connectSocketAdapter(adapterId, descriptor) {
|
|
|
1342
1361
|
}
|
|
1343
1362
|
async function startServerSocketAdapter(adapterId, descriptor, logDir) {
|
|
1344
1363
|
const port = await getFreePort(descriptor.host);
|
|
1345
|
-
const args = descriptor.args.map((arg) => arg
|
|
1364
|
+
const args = descriptor.args.map((arg) => arg.replaceAll("${port}", String(port)));
|
|
1346
1365
|
const child = spawn2(descriptor.command, args, {
|
|
1347
1366
|
cwd: descriptor.cwd,
|
|
1348
1367
|
env: descriptor.env === void 0 ? process.env : { ...process.env, ...descriptor.env },
|
|
@@ -1383,88 +1402,1243 @@ async function startServerSocketAdapter(adapterId, descriptor, logDir) {
|
|
|
1383
1402
|
logStream.end(() => resolve());
|
|
1384
1403
|
});
|
|
1385
1404
|
}
|
|
1386
|
-
};
|
|
1387
|
-
} catch (error) {
|
|
1388
|
-
await terminateChild2(child);
|
|
1389
|
-
await new Promise((resolve) => {
|
|
1390
|
-
logStream.end(() => resolve());
|
|
1405
|
+
};
|
|
1406
|
+
} catch (error) {
|
|
1407
|
+
await terminateChild2(child);
|
|
1408
|
+
await new Promise((resolve) => {
|
|
1409
|
+
logStream.end(() => resolve());
|
|
1410
|
+
});
|
|
1411
|
+
throw error;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
async function connectWithRetry(adapterId, host, port) {
|
|
1415
|
+
const deadline = Date.now() + 5e3;
|
|
1416
|
+
let lastError;
|
|
1417
|
+
while (Date.now() < deadline) {
|
|
1418
|
+
try {
|
|
1419
|
+
return await connectSocketTransport({ name: adapterId, host, port, timeoutMs: 500 });
|
|
1420
|
+
} catch (error) {
|
|
1421
|
+
lastError = error;
|
|
1422
|
+
await delay(50);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
throw lastError instanceof Error ? lastError : new Error("Timed out connecting DAP server adapter.");
|
|
1426
|
+
}
|
|
1427
|
+
function getFreePort(host) {
|
|
1428
|
+
return new Promise((resolve, reject) => {
|
|
1429
|
+
const server = net3.createServer();
|
|
1430
|
+
server.once("error", reject);
|
|
1431
|
+
server.listen(0, host, () => {
|
|
1432
|
+
const address = server.address();
|
|
1433
|
+
server.close(() => {
|
|
1434
|
+
if (typeof address === "object" && address !== null) {
|
|
1435
|
+
resolve(address.port);
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
reject(new Error("Failed to allocate a local adapter server port."));
|
|
1439
|
+
});
|
|
1440
|
+
});
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
async function terminateChild2(child) {
|
|
1444
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
child.kill("SIGTERM");
|
|
1448
|
+
if (await waitForExit2(child, 100)) {
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
child.kill("SIGKILL");
|
|
1452
|
+
await waitForExit2(child, 100);
|
|
1453
|
+
}
|
|
1454
|
+
function waitForExit2(child, timeoutMs) {
|
|
1455
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
1456
|
+
return Promise.resolve(true);
|
|
1457
|
+
}
|
|
1458
|
+
return new Promise((resolve) => {
|
|
1459
|
+
const timer = setTimeout(() => {
|
|
1460
|
+
child.off("exit", onExit);
|
|
1461
|
+
resolve(false);
|
|
1462
|
+
}, timeoutMs);
|
|
1463
|
+
const onExit = () => {
|
|
1464
|
+
clearTimeout(timer);
|
|
1465
|
+
resolve(true);
|
|
1466
|
+
};
|
|
1467
|
+
child.once("exit", onExit);
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
function appendStderrTail2(stderrTail, text) {
|
|
1471
|
+
for (const line of text.split(/\r?\n/).filter((value) => value.length > 0)) {
|
|
1472
|
+
stderrTail.push(line);
|
|
1473
|
+
while (stderrTail.length > 100) {
|
|
1474
|
+
stderrTail.shift();
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
async function delay(ms) {
|
|
1479
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// src/adapters/builtins/jsDebug.ts
|
|
1483
|
+
import { promises as fs11 } from "fs";
|
|
1484
|
+
import path14 from "path";
|
|
1485
|
+
|
|
1486
|
+
// src/cli/confirm.ts
|
|
1487
|
+
import * as readline from "readline/promises";
|
|
1488
|
+
async function confirm(options) {
|
|
1489
|
+
if (options.assumeYes) {
|
|
1490
|
+
return true;
|
|
1491
|
+
}
|
|
1492
|
+
const stdin = options.stdin ?? process.stdin;
|
|
1493
|
+
const stderr = options.stderr ?? process.stderr;
|
|
1494
|
+
if (stdin.isTTY !== true) {
|
|
1495
|
+
throw usageError("Confirmation required but stdin is not a TTY.", {
|
|
1496
|
+
code: "provision_consent_required",
|
|
1497
|
+
diagnostics: [
|
|
1498
|
+
options.question,
|
|
1499
|
+
"Re-run with `--yes` / `-y` or set `DAP_CLI_ASSUME_YES=1` to pre-consent."
|
|
1500
|
+
],
|
|
1501
|
+
data: { question: options.question }
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
stderr.write(`
|
|
1505
|
+
${options.question}
|
|
1506
|
+
`);
|
|
1507
|
+
for (const line of options.details ?? []) {
|
|
1508
|
+
stderr.write(` ${line}
|
|
1509
|
+
`);
|
|
1510
|
+
}
|
|
1511
|
+
stderr.write("Proceed? [y/N] ");
|
|
1512
|
+
const rl = readline.createInterface({ input: stdin, output: stderr, terminal: false });
|
|
1513
|
+
try {
|
|
1514
|
+
const answer = (await rl.question("")).trim().toLowerCase();
|
|
1515
|
+
if (answer === "y" || answer === "yes") {
|
|
1516
|
+
return true;
|
|
1517
|
+
}
|
|
1518
|
+
throw usageError("User declined provisioning consent.", {
|
|
1519
|
+
code: "provision_consent_declined",
|
|
1520
|
+
diagnostics: ["Re-run with `--yes` to pre-consent."],
|
|
1521
|
+
data: { question: options.question }
|
|
1522
|
+
});
|
|
1523
|
+
} finally {
|
|
1524
|
+
rl.close();
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
function resolveAssumeYes(cliYes, env) {
|
|
1528
|
+
if (cliYes === true) {
|
|
1529
|
+
return true;
|
|
1530
|
+
}
|
|
1531
|
+
const value = env.DAP_CLI_ASSUME_YES;
|
|
1532
|
+
return value === "1" || value === "true";
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
// src/adapters/provision/jsDebug.ts
|
|
1536
|
+
import path9 from "path";
|
|
1537
|
+
import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
|
|
1538
|
+
import { promises as fs6, createReadStream as createReadStream2 } from "fs";
|
|
1539
|
+
|
|
1540
|
+
// src/adapters/provision/atomicInstall.ts
|
|
1541
|
+
import path5 from "path";
|
|
1542
|
+
import { promises as fs2 } from "fs";
|
|
1543
|
+
import { randomBytes } from "crypto";
|
|
1544
|
+
var CACHE_UNWRITABLE_CODES = /* @__PURE__ */ new Set(["EACCES", "EROFS", "ENOSPC", "EPERM"]);
|
|
1545
|
+
function isCacheUnwritableError(error) {
|
|
1546
|
+
if (!(error instanceof Error) || !("code" in error)) {
|
|
1547
|
+
return false;
|
|
1548
|
+
}
|
|
1549
|
+
const code = error.code;
|
|
1550
|
+
return typeof code === "string" && CACHE_UNWRITABLE_CODES.has(code);
|
|
1551
|
+
}
|
|
1552
|
+
function cacheUnwritableError(adaptersDir, errnoCode, adapterId) {
|
|
1553
|
+
return usageError("Adapter cache directory is not writable.", {
|
|
1554
|
+
code: "provision_cache_unwritable",
|
|
1555
|
+
diagnostics: [
|
|
1556
|
+
`Adapter cache: ${adaptersDir}`,
|
|
1557
|
+
`Filesystem error: ${errnoCode}`,
|
|
1558
|
+
"Override with `DAP_CLI_ADAPTERS_DIR=<writable-path>`."
|
|
1559
|
+
],
|
|
1560
|
+
data: { adaptersDir, errnoCode, adapterId }
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
function stagingName(adapterId) {
|
|
1564
|
+
return `.${adapterId}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
1565
|
+
}
|
|
1566
|
+
async function verifyEntrypoints(stagingDir, entrypoints) {
|
|
1567
|
+
for (const rel of entrypoints) {
|
|
1568
|
+
const target = path5.join(stagingDir, rel);
|
|
1569
|
+
try {
|
|
1570
|
+
await fs2.stat(target);
|
|
1571
|
+
} catch (cause) {
|
|
1572
|
+
throw usageError("Adapter install completed but the expected entry point is missing.", {
|
|
1573
|
+
code: "provision_extract_failed",
|
|
1574
|
+
diagnostics: [`Missing entry point: ${rel}`, `Staging directory: ${stagingDir}`],
|
|
1575
|
+
data: { entrypoint: rel, stagingDir },
|
|
1576
|
+
cause
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
async function atomicInstall(options) {
|
|
1582
|
+
const { adaptersDir, adapterId, populate, expectedEntrypoints } = options;
|
|
1583
|
+
try {
|
|
1584
|
+
await fs2.mkdir(adaptersDir, { recursive: true });
|
|
1585
|
+
} catch (error) {
|
|
1586
|
+
if (isCacheUnwritableError(error)) {
|
|
1587
|
+
throw cacheUnwritableError(adaptersDir, error.code ?? "unknown", adapterId);
|
|
1588
|
+
}
|
|
1589
|
+
throw error;
|
|
1590
|
+
}
|
|
1591
|
+
const canonical = path5.join(adaptersDir, adapterId);
|
|
1592
|
+
const staging = path5.join(adaptersDir, stagingName(adapterId));
|
|
1593
|
+
try {
|
|
1594
|
+
await fs2.mkdir(staging, { recursive: true });
|
|
1595
|
+
} catch (error) {
|
|
1596
|
+
if (isCacheUnwritableError(error)) {
|
|
1597
|
+
throw cacheUnwritableError(adaptersDir, error.code ?? "unknown", adapterId);
|
|
1598
|
+
}
|
|
1599
|
+
throw error;
|
|
1600
|
+
}
|
|
1601
|
+
try {
|
|
1602
|
+
await populate(staging);
|
|
1603
|
+
await verifyEntrypoints(staging, expectedEntrypoints);
|
|
1604
|
+
await fs2.rm(canonical, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
1605
|
+
await fs2.rename(staging, canonical);
|
|
1606
|
+
return canonical;
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
await fs2.rm(staging, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }).catch(() => void 0);
|
|
1609
|
+
if (!(error instanceof CliError) && isCacheUnwritableError(error)) {
|
|
1610
|
+
throw cacheUnwritableError(adaptersDir, error.code ?? "unknown", adapterId);
|
|
1611
|
+
}
|
|
1612
|
+
throw error;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// src/adapters/provision/http.ts
|
|
1617
|
+
import { createWriteStream as createWriteStream3 } from "fs";
|
|
1618
|
+
import { promises as fs3 } from "fs";
|
|
1619
|
+
import { Readable } from "stream";
|
|
1620
|
+
import { pipeline } from "stream/promises";
|
|
1621
|
+
import path6 from "path";
|
|
1622
|
+
import { fetch, ProxyAgent } from "undici";
|
|
1623
|
+
var LOCAL_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1"]);
|
|
1624
|
+
function pickProxyEnv(env, protocol) {
|
|
1625
|
+
if (protocol === "https:") {
|
|
1626
|
+
return env.HTTPS_PROXY ?? env.https_proxy;
|
|
1627
|
+
}
|
|
1628
|
+
return env.HTTP_PROXY ?? env.http_proxy;
|
|
1629
|
+
}
|
|
1630
|
+
function matchesNoProxy(host, noProxy) {
|
|
1631
|
+
if (noProxy === void 0 || noProxy.length === 0) {
|
|
1632
|
+
return false;
|
|
1633
|
+
}
|
|
1634
|
+
const normalized = host.toLowerCase();
|
|
1635
|
+
for (const raw of noProxy.split(",")) {
|
|
1636
|
+
const pattern = raw.trim().toLowerCase();
|
|
1637
|
+
if (pattern.length === 0) {
|
|
1638
|
+
continue;
|
|
1639
|
+
}
|
|
1640
|
+
if (pattern === "*") {
|
|
1641
|
+
return true;
|
|
1642
|
+
}
|
|
1643
|
+
const stripped = pattern.startsWith(".") ? pattern.slice(1) : pattern;
|
|
1644
|
+
if (normalized === stripped || normalized.endsWith(`.${stripped}`)) {
|
|
1645
|
+
return true;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
return false;
|
|
1649
|
+
}
|
|
1650
|
+
function resolveProxy(targetUrl, env) {
|
|
1651
|
+
const proxyUrl = pickProxyEnv(env, targetUrl.protocol);
|
|
1652
|
+
if (proxyUrl === void 0 || proxyUrl.length === 0) {
|
|
1653
|
+
return { dispatcher: void 0, proxyUrl: void 0 };
|
|
1654
|
+
}
|
|
1655
|
+
if (matchesNoProxy(targetUrl.hostname, env.NO_PROXY ?? env.no_proxy)) {
|
|
1656
|
+
return { dispatcher: void 0, proxyUrl: void 0 };
|
|
1657
|
+
}
|
|
1658
|
+
return { dispatcher: new ProxyAgent(proxyUrl), proxyUrl };
|
|
1659
|
+
}
|
|
1660
|
+
function isLocalHost(host) {
|
|
1661
|
+
return LOCAL_HOSTS.has(host.toLowerCase());
|
|
1662
|
+
}
|
|
1663
|
+
function sanitizeUrl(raw) {
|
|
1664
|
+
try {
|
|
1665
|
+
const parsed = new URL(raw);
|
|
1666
|
+
parsed.username = "";
|
|
1667
|
+
parsed.password = "";
|
|
1668
|
+
parsed.search = "";
|
|
1669
|
+
parsed.hash = "";
|
|
1670
|
+
return parsed.toString();
|
|
1671
|
+
} catch {
|
|
1672
|
+
return raw.split("?")[0]?.split("#")[0] ?? raw;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
function rateLimitError(url, retryAfter) {
|
|
1676
|
+
const safeUrl = sanitizeUrl(url);
|
|
1677
|
+
return usageError("GitHub rate limit exceeded.", {
|
|
1678
|
+
code: "provision_rate_limited",
|
|
1679
|
+
diagnostics: [
|
|
1680
|
+
`URL: ${safeUrl}`,
|
|
1681
|
+
retryAfter !== null && retryAfter.length > 0 ? `Retry after epoch ${retryAfter}.` : "Retry-After header not provided.",
|
|
1682
|
+
"Set `GITHUB_TOKEN` to raise the GitHub API rate limit."
|
|
1683
|
+
],
|
|
1684
|
+
data: { url: safeUrl, status: 403, retryAfter: retryAfter ?? void 0 }
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
function networkError(url, status, statusText) {
|
|
1688
|
+
const safeUrl = sanitizeUrl(url);
|
|
1689
|
+
return usageError("Adapter download failed.", {
|
|
1690
|
+
code: "provision_network_error",
|
|
1691
|
+
diagnostics: [`URL: ${safeUrl}`, `HTTP ${status} ${statusText}`],
|
|
1692
|
+
data: { url: safeUrl, status, statusText }
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
function networkCauseError(url, code, message, cause) {
|
|
1696
|
+
const safeUrl = sanitizeUrl(url);
|
|
1697
|
+
return usageError("Adapter download failed.", {
|
|
1698
|
+
code: "provision_network_error",
|
|
1699
|
+
diagnostics: [`URL: ${safeUrl}`, code !== void 0 ? `Cause: ${code}` : `Cause: ${message}`],
|
|
1700
|
+
data: { url: safeUrl, causeCode: code },
|
|
1701
|
+
cause
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
function proxyError(url, proxyUrl, code, message, cause) {
|
|
1705
|
+
const safeUrl = sanitizeUrl(url);
|
|
1706
|
+
const safeProxy = sanitizeUrl(proxyUrl);
|
|
1707
|
+
return usageError("Adapter download failed through configured proxy.", {
|
|
1708
|
+
code: "provision_proxy_error",
|
|
1709
|
+
diagnostics: [
|
|
1710
|
+
`URL: ${safeUrl}`,
|
|
1711
|
+
`Proxy: ${safeProxy}`,
|
|
1712
|
+
code !== void 0 ? `Cause: ${code}` : `Cause: ${message}`,
|
|
1713
|
+
"Verify `HTTPS_PROXY` is correct or set `NO_PROXY=github.com` to bypass."
|
|
1714
|
+
],
|
|
1715
|
+
data: { url: safeUrl, proxyUrl: safeProxy, causeCode: code },
|
|
1716
|
+
cause
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
function extractCause(error) {
|
|
1720
|
+
if (error instanceof Error && "cause" in error) {
|
|
1721
|
+
const cause = error.cause;
|
|
1722
|
+
if (cause !== void 0 && cause !== null && typeof cause === "object") {
|
|
1723
|
+
const codeValue = cause.code;
|
|
1724
|
+
const messageValue = cause.message;
|
|
1725
|
+
const result = {};
|
|
1726
|
+
if (typeof codeValue === "string") {
|
|
1727
|
+
result.code = codeValue;
|
|
1728
|
+
}
|
|
1729
|
+
if (typeof messageValue === "string") {
|
|
1730
|
+
result.message = messageValue;
|
|
1731
|
+
}
|
|
1732
|
+
return result;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
return void 0;
|
|
1736
|
+
}
|
|
1737
|
+
async function downloadToFile(options) {
|
|
1738
|
+
const env = options.env ?? process.env;
|
|
1739
|
+
let target;
|
|
1740
|
+
try {
|
|
1741
|
+
target = new URL(options.url);
|
|
1742
|
+
} catch {
|
|
1743
|
+
throw usageError("Invalid download URL.", {
|
|
1744
|
+
code: "provision_network_error",
|
|
1745
|
+
diagnostics: [`URL: ${sanitizeUrl(options.url)}`, "URL could not be parsed."],
|
|
1746
|
+
data: { url: sanitizeUrl(options.url) }
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
if (target.protocol !== "https:" && !(target.protocol === "http:" && isLocalHost(target.hostname))) {
|
|
1750
|
+
throw usageError("Refusing non-HTTPS download URL.", {
|
|
1751
|
+
code: "provision_network_error",
|
|
1752
|
+
diagnostics: [`URL: ${sanitizeUrl(options.url)}`, "Downloads must use https:// (http://localhost is allowed for tests)."],
|
|
1753
|
+
data: { url: sanitizeUrl(options.url) }
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
const { dispatcher, proxyUrl } = resolveProxy(target, env);
|
|
1757
|
+
await fs3.mkdir(path6.dirname(options.destPath), { recursive: true });
|
|
1758
|
+
let response;
|
|
1759
|
+
try {
|
|
1760
|
+
response = await fetch(options.url, dispatcher !== void 0 ? { dispatcher } : void 0);
|
|
1761
|
+
} catch (error) {
|
|
1762
|
+
const cause = extractCause(error);
|
|
1763
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1764
|
+
if (proxyUrl !== void 0) {
|
|
1765
|
+
throw proxyError(options.url, proxyUrl, cause?.code, cause?.message ?? message, error);
|
|
1766
|
+
}
|
|
1767
|
+
throw networkCauseError(options.url, cause?.code, cause?.message ?? message, error);
|
|
1768
|
+
}
|
|
1769
|
+
if (!response.ok) {
|
|
1770
|
+
if (response.status === 403 && response.headers.get("x-ratelimit-remaining") === "0") {
|
|
1771
|
+
throw rateLimitError(options.url, response.headers.get("x-ratelimit-reset"));
|
|
1772
|
+
}
|
|
1773
|
+
throw networkError(options.url, response.status, response.statusText);
|
|
1774
|
+
}
|
|
1775
|
+
if (response.body === null) {
|
|
1776
|
+
throw usageError("Adapter download returned an empty body.", {
|
|
1777
|
+
code: "provision_network_error",
|
|
1778
|
+
diagnostics: [`URL: ${sanitizeUrl(options.url)}`],
|
|
1779
|
+
data: { url: sanitizeUrl(options.url) }
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
const contentLengthHeader = response.headers.get("content-length");
|
|
1783
|
+
const totalBytes = contentLengthHeader !== null ? Number.parseInt(contentLengthHeader, 10) : void 0;
|
|
1784
|
+
const total = Number.isFinite(totalBytes) ? totalBytes : void 0;
|
|
1785
|
+
let bytesRead = 0;
|
|
1786
|
+
const onProgress = options.onProgress;
|
|
1787
|
+
const body = Readable.fromWeb(response.body);
|
|
1788
|
+
if (onProgress !== void 0) {
|
|
1789
|
+
body.on("data", (chunk) => {
|
|
1790
|
+
bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
|
|
1791
|
+
onProgress(bytesRead, total);
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
try {
|
|
1795
|
+
await pipeline(body, createWriteStream3(options.destPath));
|
|
1796
|
+
} catch (error) {
|
|
1797
|
+
await fs3.rm(options.destPath, { force: true }).catch(() => void 0);
|
|
1798
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1799
|
+
throw usageError("Adapter download stream failed.", {
|
|
1800
|
+
code: "provision_network_error",
|
|
1801
|
+
diagnostics: [`URL: ${sanitizeUrl(options.url)}`, message],
|
|
1802
|
+
data: { url: sanitizeUrl(options.url) },
|
|
1803
|
+
cause: error
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
// src/adapters/provision/extractTarGz.ts
|
|
1809
|
+
import { createReadStream } from "fs";
|
|
1810
|
+
import * as tar from "tar";
|
|
1811
|
+
async function extractTarGz(archivePath, destDir, options = {}) {
|
|
1812
|
+
await new Promise((resolve, reject) => {
|
|
1813
|
+
const source = createReadStream(archivePath);
|
|
1814
|
+
const extract = tar.x({
|
|
1815
|
+
cwd: destDir,
|
|
1816
|
+
strict: true,
|
|
1817
|
+
strip: options.strip ?? 0
|
|
1818
|
+
});
|
|
1819
|
+
source.on("error", reject);
|
|
1820
|
+
extract.on("error", reject);
|
|
1821
|
+
extract.on("finish", resolve);
|
|
1822
|
+
source.pipe(extract);
|
|
1823
|
+
}).catch((error) => {
|
|
1824
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1825
|
+
throw usageError("Archive extraction failed.", {
|
|
1826
|
+
code: "provision_extract_failed",
|
|
1827
|
+
diagnostics: [archivePath, message],
|
|
1828
|
+
data: { archivePath },
|
|
1829
|
+
cause: error
|
|
1830
|
+
});
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// src/adapters/provision/lock.ts
|
|
1835
|
+
import path7 from "path";
|
|
1836
|
+
import { promises as fs4 } from "fs";
|
|
1837
|
+
import * as lockfile from "proper-lockfile";
|
|
1838
|
+
var PRODUCTION_RETRY = {
|
|
1839
|
+
retries: 60,
|
|
1840
|
+
minTimeout: 500,
|
|
1841
|
+
maxTimeout: 2e3,
|
|
1842
|
+
factor: 1
|
|
1843
|
+
};
|
|
1844
|
+
var STALE_MS = 5 * 60 * 1e3;
|
|
1845
|
+
var CACHE_UNWRITABLE_CODES2 = /* @__PURE__ */ new Set(["EACCES", "EROFS", "ENOSPC", "EPERM"]);
|
|
1846
|
+
function getErrnoCode(error) {
|
|
1847
|
+
if (error instanceof Error && "code" in error) {
|
|
1848
|
+
const code = error.code;
|
|
1849
|
+
if (typeof code === "string") {
|
|
1850
|
+
return code;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
return void 0;
|
|
1854
|
+
}
|
|
1855
|
+
function cacheUnwritableError2(adaptersDir, errnoCode, adapterId) {
|
|
1856
|
+
return usageError("Adapter cache directory is not writable.", {
|
|
1857
|
+
code: "provision_cache_unwritable",
|
|
1858
|
+
diagnostics: [
|
|
1859
|
+
`Adapter cache: ${adaptersDir}`,
|
|
1860
|
+
`Filesystem error: ${errnoCode}`,
|
|
1861
|
+
"Override with `DAP_CLI_ADAPTERS_DIR=<writable-path>`."
|
|
1862
|
+
],
|
|
1863
|
+
data: { adaptersDir, errnoCode, adapterId }
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
function resolveRetry(options) {
|
|
1867
|
+
if (options?.retryOverride !== void 0) {
|
|
1868
|
+
return options.retryOverride;
|
|
1869
|
+
}
|
|
1870
|
+
const env = process.env.DAP_CLI_LOCK_RETRY_OVERRIDE;
|
|
1871
|
+
if (env !== void 0 && env.length > 0) {
|
|
1872
|
+
try {
|
|
1873
|
+
const parsed = JSON.parse(env);
|
|
1874
|
+
if (parsed !== null && typeof parsed === "object") {
|
|
1875
|
+
const candidate = parsed;
|
|
1876
|
+
const retries = candidate.retries;
|
|
1877
|
+
const minTimeout = candidate.minTimeout;
|
|
1878
|
+
const maxTimeout = candidate.maxTimeout;
|
|
1879
|
+
const factor = candidate.factor;
|
|
1880
|
+
if (typeof retries === "number" && typeof minTimeout === "number" && typeof maxTimeout === "number" && typeof factor === "number") {
|
|
1881
|
+
return { retries, minTimeout, maxTimeout, factor };
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
} catch {
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
return PRODUCTION_RETRY;
|
|
1888
|
+
}
|
|
1889
|
+
function lockTimeoutError(adapterId, sentinel) {
|
|
1890
|
+
return usageError("Timed out waiting for adapter install lock.", {
|
|
1891
|
+
code: "provision_lock_timeout",
|
|
1892
|
+
diagnostics: [
|
|
1893
|
+
`Adapter: ${adapterId}`,
|
|
1894
|
+
`Lock sentinel: ${sentinel}`,
|
|
1895
|
+
`Another dap-cli process may be installing; if none, delete ${sentinel} and retry.`
|
|
1896
|
+
],
|
|
1897
|
+
data: { adapterId, sentinel }
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
async function withAdapterLock(adaptersDir, adapterId, fn, options) {
|
|
1901
|
+
try {
|
|
1902
|
+
await fs4.mkdir(adaptersDir, { recursive: true });
|
|
1903
|
+
} catch (error) {
|
|
1904
|
+
const errnoCode = getErrnoCode(error);
|
|
1905
|
+
if (errnoCode !== void 0 && CACHE_UNWRITABLE_CODES2.has(errnoCode)) {
|
|
1906
|
+
throw cacheUnwritableError2(adaptersDir, errnoCode, adapterId);
|
|
1907
|
+
}
|
|
1908
|
+
throw error;
|
|
1909
|
+
}
|
|
1910
|
+
const sentinel = path7.join(adaptersDir, `.${adapterId}.lock-target`);
|
|
1911
|
+
try {
|
|
1912
|
+
await fs4.writeFile(sentinel, "", { flag: "a" });
|
|
1913
|
+
} catch (error) {
|
|
1914
|
+
const errnoCode = getErrnoCode(error);
|
|
1915
|
+
if (errnoCode !== void 0 && CACHE_UNWRITABLE_CODES2.has(errnoCode)) {
|
|
1916
|
+
throw cacheUnwritableError2(adaptersDir, errnoCode, adapterId);
|
|
1917
|
+
}
|
|
1918
|
+
throw error;
|
|
1919
|
+
}
|
|
1920
|
+
const retry = resolveRetry(options);
|
|
1921
|
+
let release;
|
|
1922
|
+
try {
|
|
1923
|
+
release = await lockfile.lock(sentinel, {
|
|
1924
|
+
stale: STALE_MS,
|
|
1925
|
+
realpath: false,
|
|
1926
|
+
retries: { ...retry }
|
|
1927
|
+
});
|
|
1928
|
+
} catch (error) {
|
|
1929
|
+
const errnoCode = getErrnoCode(error);
|
|
1930
|
+
if (errnoCode === "ELOCKED" || errnoCode === "EEXIST") {
|
|
1931
|
+
throw lockTimeoutError(adapterId, sentinel);
|
|
1932
|
+
}
|
|
1933
|
+
if (errnoCode !== void 0 && CACHE_UNWRITABLE_CODES2.has(errnoCode)) {
|
|
1934
|
+
throw cacheUnwritableError2(adaptersDir, errnoCode, adapterId);
|
|
1935
|
+
}
|
|
1936
|
+
throw error;
|
|
1937
|
+
}
|
|
1938
|
+
try {
|
|
1939
|
+
return await fn();
|
|
1940
|
+
} finally {
|
|
1941
|
+
if (release !== void 0) {
|
|
1942
|
+
await release().catch(() => void 0);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// src/adapters/provision/consent.ts
|
|
1948
|
+
import path8 from "path";
|
|
1949
|
+
import { promises as fs5 } from "fs";
|
|
1950
|
+
function markerPath(adaptersDir, adapterId, version) {
|
|
1951
|
+
return path8.join(adaptersDir, adapterId, `.consent-${version}`);
|
|
1952
|
+
}
|
|
1953
|
+
async function hasConsentMarker(adaptersDir, adapterId, version) {
|
|
1954
|
+
try {
|
|
1955
|
+
await fs5.stat(markerPath(adaptersDir, adapterId, version));
|
|
1956
|
+
return true;
|
|
1957
|
+
} catch {
|
|
1958
|
+
return false;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
async function writeConsentMarker(adaptersDir, adapterId, version) {
|
|
1962
|
+
const filePath = markerPath(adaptersDir, adapterId, version);
|
|
1963
|
+
await fs5.mkdir(path8.dirname(filePath), { recursive: true });
|
|
1964
|
+
await fs5.writeFile(filePath, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1965
|
+
`, { flag: "w" });
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
// src/adapters/provision/checksums.ts
|
|
1969
|
+
var JS_DEBUG_VERSION = "1.117.0";
|
|
1970
|
+
var DEBUGPY_VERSION = "1.8.20";
|
|
1971
|
+
var DELVE_VERSION = "v1.26.3";
|
|
1972
|
+
var CODELLDB_VERSION = "v1.12.2";
|
|
1973
|
+
var JS_DEBUG_CHECKSUMS = {
|
|
1974
|
+
"1.117.0": "ad8d04ede9d4b75cc290fd5438a65047a06f786d04f604b6112485b36f090772"
|
|
1975
|
+
};
|
|
1976
|
+
var DELVE_CHECKSUMS = {
|
|
1977
|
+
"v1.26.3": {
|
|
1978
|
+
darwin_arm64: "7f28483a42f0a911f29b236aa40d24d7099f1b0ec54c56c4d439a6903d478a3d",
|
|
1979
|
+
darwin_amd64: "6827a438473167a1e0805b4546e5bf2d53401530f694deb35e41c6e7b46e27c8",
|
|
1980
|
+
linux_amd64: "cdd4d6b2a638d8f26468d82a76b766df594641490bea566629305d90fbccc06e",
|
|
1981
|
+
linux_arm64: "5b03fd74895d676c4435bec1aade0863be1489a4be1bb5c9269c6ef389bf5d2d",
|
|
1982
|
+
windows_amd64: "f9e15b8f3628e4c7bfe481011bea458df754d0e75c6ff4ab01c71294165950fd"
|
|
1983
|
+
}
|
|
1984
|
+
};
|
|
1985
|
+
var CODELLDB_CHECKSUMS = {
|
|
1986
|
+
"v1.12.2": {
|
|
1987
|
+
darwin_arm64: "c836b81c6f2da467b5920a376a7bfc849dc4b4d81b19779dedf1c685cb4aa1a0"
|
|
1988
|
+
}
|
|
1989
|
+
};
|
|
1990
|
+
|
|
1991
|
+
// src/adapters/provision/jsDebug.ts
|
|
1992
|
+
var ADAPTER_ID = "js-debug";
|
|
1993
|
+
var ENTRYPOINTS = ["src/dapDebugServer.js", "src/bootloader.js"];
|
|
1994
|
+
var PACKAGE_BOUNDARY = '{"type":"commonjs"}\n';
|
|
1995
|
+
function resolveBaseUrl(env) {
|
|
1996
|
+
const override = env.DAP_CLI_PROVISION_RELEASE_BASE_URL;
|
|
1997
|
+
return override !== void 0 && override.length > 0 ? override : "https://github.com";
|
|
1998
|
+
}
|
|
1999
|
+
function downloadUrl(env) {
|
|
2000
|
+
return `${resolveBaseUrl(env)}/microsoft/vscode-js-debug/releases/download/v${JS_DEBUG_VERSION}/js-debug-dap-v${JS_DEBUG_VERSION}.tar.gz`;
|
|
2001
|
+
}
|
|
2002
|
+
async function fileSha256(filePath) {
|
|
2003
|
+
return await new Promise((resolve, reject) => {
|
|
2004
|
+
const hash = createHash2("sha256");
|
|
2005
|
+
const stream = createReadStream2(filePath);
|
|
2006
|
+
stream.on("error", reject);
|
|
2007
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
2008
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
async function entrypointsExist(installRoot) {
|
|
2012
|
+
for (const rel of ENTRYPOINTS) {
|
|
2013
|
+
try {
|
|
2014
|
+
await fs6.access(path9.join(installRoot, rel));
|
|
2015
|
+
} catch {
|
|
2016
|
+
return false;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
return true;
|
|
2020
|
+
}
|
|
2021
|
+
async function provisionJsDebug(ctx) {
|
|
2022
|
+
const installRoot = path9.join(ctx.adaptersDir, ADAPTER_ID);
|
|
2023
|
+
const entrypoint = path9.join(installRoot, "src", "dapDebugServer.js");
|
|
2024
|
+
if (await hasConsentMarker(ctx.adaptersDir, ADAPTER_ID, JS_DEBUG_VERSION) && await entrypointsExist(installRoot)) {
|
|
2025
|
+
return {
|
|
2026
|
+
adapterId: "js-debug",
|
|
2027
|
+
version: JS_DEBUG_VERSION,
|
|
2028
|
+
installRoot,
|
|
2029
|
+
entrypoint,
|
|
2030
|
+
fromCache: true
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
const url = downloadUrl(ctx.env);
|
|
2034
|
+
const expectedSha = JS_DEBUG_CHECKSUMS[JS_DEBUG_VERSION];
|
|
2035
|
+
if (expectedSha === void 0) {
|
|
2036
|
+
throw usageError(`No SHA-256 checksum recorded for js-debug v${JS_DEBUG_VERSION}.`, {
|
|
2037
|
+
code: "provision_checksum_mismatch",
|
|
2038
|
+
diagnostics: [
|
|
2039
|
+
`Adapter: js-debug ${JS_DEBUG_VERSION}`,
|
|
2040
|
+
"Edit src/adapters/provision/checksums.ts and add the hash for this version.",
|
|
2041
|
+
"Re-run setup or report at https://github.com/roblourens/dap-cli/issues if persistent."
|
|
2042
|
+
],
|
|
2043
|
+
data: {
|
|
2044
|
+
adapterId: "js-debug",
|
|
2045
|
+
version: JS_DEBUG_VERSION
|
|
2046
|
+
}
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
await confirm({
|
|
2050
|
+
assumeYes: ctx.assumeYes,
|
|
2051
|
+
question: `Install vscode-js-debug ${JS_DEBUG_VERSION} into ${installRoot} (~10MB)?`,
|
|
2052
|
+
details: [`Source: ${url}`],
|
|
2053
|
+
...ctx.stdin === void 0 ? {} : { stdin: ctx.stdin },
|
|
2054
|
+
...ctx.stderr === void 0 ? {} : { stderr: ctx.stderr }
|
|
2055
|
+
});
|
|
2056
|
+
await withAdapterLock(ctx.adaptersDir, ADAPTER_ID, async () => {
|
|
2057
|
+
if (await hasConsentMarker(ctx.adaptersDir, ADAPTER_ID, JS_DEBUG_VERSION) && await entrypointsExist(installRoot)) {
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
await atomicInstall({
|
|
2061
|
+
adaptersDir: ctx.adaptersDir,
|
|
2062
|
+
adapterId: ADAPTER_ID,
|
|
2063
|
+
expectedEntrypoints: ENTRYPOINTS,
|
|
2064
|
+
populate: async (stagingDir) => {
|
|
2065
|
+
const archivePath = path9.join(
|
|
2066
|
+
ctx.adaptersDir,
|
|
2067
|
+
`.${ADAPTER_ID}.archive.${process.pid}.${randomBytes2(4).toString("hex")}.tar.gz`
|
|
2068
|
+
);
|
|
2069
|
+
try {
|
|
2070
|
+
await downloadToFile({ url, destPath: archivePath, env: ctx.env });
|
|
2071
|
+
const actualSha = await fileSha256(archivePath);
|
|
2072
|
+
if (actualSha !== expectedSha) {
|
|
2073
|
+
throw usageError("Adapter download failed SHA-256 verification.", {
|
|
2074
|
+
code: "provision_checksum_mismatch",
|
|
2075
|
+
diagnostics: [
|
|
2076
|
+
`URL: ${url}`,
|
|
2077
|
+
`Expected: ${expectedSha}`,
|
|
2078
|
+
`Actual: ${actualSha}`,
|
|
2079
|
+
"Re-run setup or report at https://github.com/roblourens/dap-cli/issues if persistent."
|
|
2080
|
+
],
|
|
2081
|
+
data: {
|
|
2082
|
+
adapterId: "js-debug",
|
|
2083
|
+
version: JS_DEBUG_VERSION,
|
|
2084
|
+
url,
|
|
2085
|
+
expectedSha,
|
|
2086
|
+
actualSha
|
|
2087
|
+
}
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
await extractTarGz(archivePath, stagingDir, { strip: 1 });
|
|
2091
|
+
await fs6.writeFile(
|
|
2092
|
+
path9.join(stagingDir, "package.json"),
|
|
2093
|
+
PACKAGE_BOUNDARY,
|
|
2094
|
+
"utf8"
|
|
2095
|
+
);
|
|
2096
|
+
} finally {
|
|
2097
|
+
await fs6.rm(archivePath, { force: true }).catch(() => void 0);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
});
|
|
2101
|
+
await writeConsentMarker(ctx.adaptersDir, ADAPTER_ID, JS_DEBUG_VERSION);
|
|
2102
|
+
});
|
|
2103
|
+
return {
|
|
2104
|
+
adapterId: "js-debug",
|
|
2105
|
+
version: JS_DEBUG_VERSION,
|
|
2106
|
+
installRoot,
|
|
2107
|
+
entrypoint,
|
|
2108
|
+
fromCache: false
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
// src/adapters/provision/debugpy.ts
|
|
2113
|
+
import path10 from "path";
|
|
2114
|
+
import { promises as fs7 } from "fs";
|
|
2115
|
+
import { execFile } from "child_process";
|
|
2116
|
+
import { promisify } from "util";
|
|
2117
|
+
var execFileAsync = promisify(execFile);
|
|
2118
|
+
function venvPythonRel() {
|
|
2119
|
+
return process.platform === "win32" ? path10.join("venv", "Scripts", "python.exe") : path10.join("venv", "bin", "python");
|
|
2120
|
+
}
|
|
2121
|
+
function venvPipRel() {
|
|
2122
|
+
return process.platform === "win32" ? path10.join("venv", "Scripts", "pip.exe") : path10.join("venv", "bin", "pip");
|
|
2123
|
+
}
|
|
2124
|
+
function tail(text, max = 2048) {
|
|
2125
|
+
if (text.length <= max) {
|
|
2126
|
+
return text;
|
|
2127
|
+
}
|
|
2128
|
+
return `...${text.slice(text.length - max)}`;
|
|
2129
|
+
}
|
|
2130
|
+
async function exists(p) {
|
|
2131
|
+
try {
|
|
2132
|
+
await fs7.access(p);
|
|
2133
|
+
return true;
|
|
2134
|
+
} catch {
|
|
2135
|
+
return false;
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
async function provisionDebugpy(ctx) {
|
|
2139
|
+
const { env, assumeYes, adaptersDir, stdin, stderr } = ctx;
|
|
2140
|
+
const installRoot = path10.join(adaptersDir, "debugpy");
|
|
2141
|
+
const entrypoint = path10.join(installRoot, venvPythonRel());
|
|
2142
|
+
if (await hasConsentMarker(adaptersDir, "debugpy", DEBUGPY_VERSION) && await exists(entrypoint)) {
|
|
2143
|
+
return {
|
|
2144
|
+
adapterId: "debugpy",
|
|
2145
|
+
version: DEBUGPY_VERSION,
|
|
2146
|
+
installRoot,
|
|
2147
|
+
entrypoint,
|
|
2148
|
+
fromCache: true
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
await confirm({
|
|
2152
|
+
assumeYes,
|
|
2153
|
+
question: `Install debugpy ${DEBUGPY_VERSION} into a venv at ${installRoot}/ (~6MB)?`,
|
|
2154
|
+
details: [
|
|
2155
|
+
"Requires python3 (>=3.8) on PATH.",
|
|
2156
|
+
"Creates an isolated venv and pip-installs debugpy. The venv python becomes the adapter command."
|
|
2157
|
+
],
|
|
2158
|
+
...stdin === void 0 ? {} : { stdin },
|
|
2159
|
+
...stderr === void 0 ? {} : { stderr }
|
|
2160
|
+
});
|
|
2161
|
+
await withAdapterLock(adaptersDir, "debugpy", async () => {
|
|
2162
|
+
if (await hasConsentMarker(adaptersDir, "debugpy", DEBUGPY_VERSION) && await exists(entrypoint)) {
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
const python3 = env.DAP_CLI_PROVISION_PYTHON3 ?? "python3";
|
|
2166
|
+
try {
|
|
2167
|
+
await execFileAsync(python3, ["--version"]);
|
|
2168
|
+
} catch (error) {
|
|
2169
|
+
const message = error.stderr ?? error.message ?? "unknown error";
|
|
2170
|
+
throw usageError("python3 is not available on PATH.", {
|
|
2171
|
+
code: "provision_python3_missing",
|
|
2172
|
+
diagnostics: [
|
|
2173
|
+
"Install Python 3.8+ and ensure `python3` is on PATH.",
|
|
2174
|
+
"macOS: `brew install python`",
|
|
2175
|
+
"Ubuntu/Debian: `apt install python3 python3-venv`",
|
|
2176
|
+
'Windows: install from python.org and check "Add to PATH".',
|
|
2177
|
+
`Underlying error: ${tail(message, 512)}`
|
|
2178
|
+
],
|
|
2179
|
+
data: { python3 },
|
|
2180
|
+
cause: error
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
await atomicInstall({
|
|
2184
|
+
adaptersDir,
|
|
2185
|
+
adapterId: "debugpy",
|
|
2186
|
+
expectedEntrypoints: [venvPythonRel()],
|
|
2187
|
+
populate: async (stagingDir) => {
|
|
2188
|
+
const venvDir = path10.join(stagingDir, "venv");
|
|
2189
|
+
try {
|
|
2190
|
+
await execFileAsync(python3, ["-m", "venv", venvDir]);
|
|
2191
|
+
} catch (error) {
|
|
2192
|
+
const stderrText = error.stderr ?? "";
|
|
2193
|
+
throw usageError("Failed to create Python virtual environment.", {
|
|
2194
|
+
code: "provision_python3_venv_unavailable",
|
|
2195
|
+
diagnostics: [
|
|
2196
|
+
"The `python3 -m venv` command failed. On Debian/Ubuntu install `python3-venv`:",
|
|
2197
|
+
" sudo apt install python3-venv",
|
|
2198
|
+
"On other distros ensure the standard library `venv` and `ensurepip` modules are present.",
|
|
2199
|
+
`stderr tail: ${tail(stderrText)}`
|
|
2200
|
+
],
|
|
2201
|
+
data: { python3 },
|
|
2202
|
+
cause: error
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
2205
|
+
const pipPath = path10.join(stagingDir, venvPipRel());
|
|
2206
|
+
try {
|
|
2207
|
+
await execFileAsync(pipPath, [
|
|
2208
|
+
"install",
|
|
2209
|
+
"--no-warn-script-location",
|
|
2210
|
+
"--disable-pip-version-check",
|
|
2211
|
+
`debugpy==${DEBUGPY_VERSION}`
|
|
2212
|
+
]);
|
|
2213
|
+
} catch (error) {
|
|
2214
|
+
const stderrText = error.stderr ?? "";
|
|
2215
|
+
throw usageError(`Failed to install debugpy==${DEBUGPY_VERSION} via pip.`, {
|
|
2216
|
+
code: "provision_pip_install_failed",
|
|
2217
|
+
diagnostics: [
|
|
2218
|
+
"pip install failed. Common causes: no network access, restricted PyPI mirror, missing build tools.",
|
|
2219
|
+
"Workaround: set `PIP_INDEX_URL` to your mirror, or pre-install debugpy into the venv and re-run.",
|
|
2220
|
+
`Underlying pip command: ${pipPath} install debugpy==${DEBUGPY_VERSION}`,
|
|
2221
|
+
`stderr tail: ${tail(stderrText)}`
|
|
2222
|
+
],
|
|
2223
|
+
data: { pipPath, version: DEBUGPY_VERSION },
|
|
2224
|
+
cause: error
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
});
|
|
2229
|
+
await writeConsentMarker(adaptersDir, "debugpy", DEBUGPY_VERSION);
|
|
2230
|
+
});
|
|
2231
|
+
return {
|
|
2232
|
+
adapterId: "debugpy",
|
|
2233
|
+
version: DEBUGPY_VERSION,
|
|
2234
|
+
installRoot,
|
|
2235
|
+
entrypoint,
|
|
2236
|
+
fromCache: false
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
// src/adapters/provision/delve.ts
|
|
2241
|
+
import path12 from "path";
|
|
2242
|
+
import { promises as fs9 } from "fs";
|
|
2243
|
+
import { createHash as createHash3 } from "crypto";
|
|
2244
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
2245
|
+
|
|
2246
|
+
// src/adapters/provision/extractZip.ts
|
|
2247
|
+
import { promises as fs8, createWriteStream as createWriteStream4 } from "fs";
|
|
2248
|
+
import path11 from "path";
|
|
2249
|
+
import yauzl from "yauzl";
|
|
2250
|
+
var SYMLINK_MODE = 40960;
|
|
2251
|
+
var UNIX_MODE_MASK = 61440;
|
|
2252
|
+
function isUnsafeFileName(fileName) {
|
|
2253
|
+
if (path11.isAbsolute(fileName)) {
|
|
2254
|
+
return true;
|
|
2255
|
+
}
|
|
2256
|
+
if (/^[A-Za-z]:[\\/]/.test(fileName)) {
|
|
2257
|
+
return true;
|
|
2258
|
+
}
|
|
2259
|
+
return fileName.split(/[\\/]/).includes("..");
|
|
2260
|
+
}
|
|
2261
|
+
function isSymlinkEntry(entry) {
|
|
2262
|
+
const unixMode = entry.externalFileAttributes >>> 16 & UNIX_MODE_MASK;
|
|
2263
|
+
return unixMode === SYMLINK_MODE;
|
|
2264
|
+
}
|
|
2265
|
+
function unsafeEntryError(archivePath, fileName) {
|
|
2266
|
+
return usageError("Archive contains unsafe entry path.", {
|
|
2267
|
+
code: "provision_extract_failed",
|
|
2268
|
+
diagnostics: [`Entry: ${fileName}`, `Archive: ${archivePath}`],
|
|
2269
|
+
data: { archivePath, entry: fileName }
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
function wrapExtractError(archivePath, error) {
|
|
2273
|
+
if (error instanceof CliError) {
|
|
2274
|
+
return error;
|
|
2275
|
+
}
|
|
2276
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2277
|
+
return usageError("Archive extraction failed.", {
|
|
2278
|
+
code: "provision_extract_failed",
|
|
2279
|
+
diagnostics: [archivePath, message],
|
|
2280
|
+
data: { archivePath },
|
|
2281
|
+
cause: error
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
async function extractZip(archivePath, destDir) {
|
|
2285
|
+
await fs8.mkdir(destDir, { recursive: true });
|
|
2286
|
+
await new Promise((resolve, reject) => {
|
|
2287
|
+
yauzl.open(archivePath, { lazyEntries: true }, (openErr, zip) => {
|
|
2288
|
+
if (openErr || zip === void 0) {
|
|
2289
|
+
reject(openErr ?? new Error("zip open failed"));
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
const handleError = (err) => {
|
|
2293
|
+
try {
|
|
2294
|
+
zip.close();
|
|
2295
|
+
} catch {
|
|
2296
|
+
}
|
|
2297
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
2298
|
+
};
|
|
2299
|
+
zip.on("error", handleError);
|
|
2300
|
+
zip.on("end", () => {
|
|
2301
|
+
resolve();
|
|
2302
|
+
});
|
|
2303
|
+
zip.on("entry", (entry) => {
|
|
2304
|
+
if (isUnsafeFileName(entry.fileName)) {
|
|
2305
|
+
handleError(unsafeEntryError(archivePath, entry.fileName));
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
if (isSymlinkEntry(entry)) {
|
|
2309
|
+
handleError(unsafeEntryError(archivePath, entry.fileName));
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
const dest = path11.join(destDir, entry.fileName);
|
|
2313
|
+
if (entry.fileName.endsWith("/")) {
|
|
2314
|
+
fs8.mkdir(dest, { recursive: true }).then(() => {
|
|
2315
|
+
zip.readEntry();
|
|
2316
|
+
}).catch(handleError);
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
fs8.mkdir(path11.dirname(dest), { recursive: true }).then(() => {
|
|
2320
|
+
zip.openReadStream(entry, (readErr, readStream) => {
|
|
2321
|
+
if (readErr || readStream === void 0) {
|
|
2322
|
+
handleError(readErr ?? new Error("zip read failed"));
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
const out = createWriteStream4(dest);
|
|
2326
|
+
out.on("finish", () => {
|
|
2327
|
+
zip.readEntry();
|
|
2328
|
+
});
|
|
2329
|
+
out.on("error", handleError);
|
|
2330
|
+
readStream.on("error", handleError);
|
|
2331
|
+
readStream.pipe(out);
|
|
2332
|
+
});
|
|
2333
|
+
}).catch(handleError);
|
|
2334
|
+
});
|
|
2335
|
+
zip.readEntry();
|
|
2336
|
+
});
|
|
2337
|
+
}).catch((error) => {
|
|
2338
|
+
throw wrapExtractError(archivePath, error);
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
// src/adapters/provision/delve.ts
|
|
2343
|
+
var DEFAULT_RELEASE_BASE_URL = "https://github.com";
|
|
2344
|
+
function bareVersion() {
|
|
2345
|
+
return DELVE_VERSION.startsWith("v") ? DELVE_VERSION.slice(1) : DELVE_VERSION;
|
|
2346
|
+
}
|
|
2347
|
+
function resolveDelveAsset(env) {
|
|
2348
|
+
const override = env.DAP_CLI_PROVISION_DELVE_PLATFORM_OVERRIDE;
|
|
2349
|
+
const key = override !== void 0 && override.length > 0 ? override : `${process.platform}_${process.arch}`;
|
|
2350
|
+
const matrix = {
|
|
2351
|
+
"darwin_arm64": "darwin_arm64",
|
|
2352
|
+
"darwin_x64": "darwin_amd64",
|
|
2353
|
+
"linux_x64": "linux_amd64",
|
|
2354
|
+
"linux_arm64": "linux_arm64",
|
|
2355
|
+
"win32_x64": "windows_amd64"
|
|
2356
|
+
};
|
|
2357
|
+
const platformKey = matrix[key];
|
|
2358
|
+
if (platformKey === void 0) {
|
|
2359
|
+
throw usageError(`Delve provisioning does not support platform '${key}'.`, {
|
|
2360
|
+
code: "provision_arch_unsupported",
|
|
2361
|
+
diagnostics: [
|
|
2362
|
+
`Detected platform: ${key}`,
|
|
2363
|
+
"Supported platforms: darwin_arm64, darwin_x64, linux_x64, linux_arm64, win32_x64.",
|
|
2364
|
+
"Install `dlv` manually on PATH or provision a compatible binary."
|
|
2365
|
+
],
|
|
2366
|
+
data: {
|
|
2367
|
+
adapterId: "delve",
|
|
2368
|
+
detected: key,
|
|
2369
|
+
supported: ["darwin_arm64", "darwin_x64", "linux_x64", "linux_arm64", "win32_x64"]
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
const archiveKind = platformKey === "windows_amd64" ? "zip" : "tar.gz";
|
|
2374
|
+
const ext = archiveKind === "zip" ? "zip" : "tar.gz";
|
|
2375
|
+
const archiveName = `dlv_${bareVersion()}_${platformKey}.${ext}`;
|
|
2376
|
+
const executableName = platformKey === "windows_amd64" ? "dlv.exe" : "dlv";
|
|
2377
|
+
return { platformKey, archiveName, archiveKind, executableName };
|
|
2378
|
+
}
|
|
2379
|
+
async function exists2(p) {
|
|
2380
|
+
try {
|
|
2381
|
+
await fs9.access(p);
|
|
2382
|
+
return true;
|
|
2383
|
+
} catch {
|
|
2384
|
+
return false;
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
async function computeSha256(filePath) {
|
|
2388
|
+
const buffer = await fs9.readFile(filePath);
|
|
2389
|
+
return createHash3("sha256").update(buffer).digest("hex");
|
|
2390
|
+
}
|
|
2391
|
+
async function provisionDelve(ctx) {
|
|
2392
|
+
const { env, assumeYes, adaptersDir, stdin, stderr } = ctx;
|
|
2393
|
+
const asset = resolveDelveAsset(env);
|
|
2394
|
+
const installRoot = path12.join(adaptersDir, "delve");
|
|
2395
|
+
const entrypoint = path12.join(installRoot, asset.executableName);
|
|
2396
|
+
const expectedSha = DELVE_CHECKSUMS[DELVE_VERSION]?.[asset.platformKey];
|
|
2397
|
+
if (expectedSha === void 0) {
|
|
2398
|
+
throw usageError(`No pinned SHA-256 for delve ${DELVE_VERSION} on ${asset.platformKey}.`, {
|
|
2399
|
+
code: "provision_checksum_mismatch",
|
|
2400
|
+
diagnostics: [
|
|
2401
|
+
`Adapter: delve ${DELVE_VERSION} (${asset.platformKey})`,
|
|
2402
|
+
"src/adapters/provision/checksums.ts must list a checksum for every supported platform.",
|
|
2403
|
+
"Re-run setup or report at https://github.com/roblourens/dap-cli/issues if persistent."
|
|
2404
|
+
],
|
|
2405
|
+
data: {
|
|
2406
|
+
adapterId: "delve",
|
|
2407
|
+
version: DELVE_VERSION,
|
|
2408
|
+
platform: asset.platformKey
|
|
2409
|
+
}
|
|
2410
|
+
});
|
|
2411
|
+
}
|
|
2412
|
+
if (await hasConsentMarker(adaptersDir, "delve", DELVE_VERSION) && await exists2(entrypoint)) {
|
|
2413
|
+
return {
|
|
2414
|
+
adapterId: "delve",
|
|
2415
|
+
version: DELVE_VERSION,
|
|
2416
|
+
installRoot,
|
|
2417
|
+
entrypoint,
|
|
2418
|
+
fromCache: true
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
await confirm({
|
|
2422
|
+
assumeYes,
|
|
2423
|
+
question: `Install delve ${DELVE_VERSION} into ${installRoot}/ (~10MB)?`,
|
|
2424
|
+
details: [
|
|
2425
|
+
`Downloads the official release asset ${asset.archiveName} from github.com/go-delve/delve.`,
|
|
2426
|
+
"The archive SHA-256 is verified against an embedded checksum before installation."
|
|
2427
|
+
],
|
|
2428
|
+
...stdin === void 0 ? {} : { stdin },
|
|
2429
|
+
...stderr === void 0 ? {} : { stderr }
|
|
2430
|
+
});
|
|
2431
|
+
await withAdapterLock(adaptersDir, "delve", async () => {
|
|
2432
|
+
if (await hasConsentMarker(adaptersDir, "delve", DELVE_VERSION) && await exists2(entrypoint)) {
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
const releaseBase = env.DAP_CLI_PROVISION_RELEASE_BASE_URL ?? DEFAULT_RELEASE_BASE_URL;
|
|
2436
|
+
const url = `${releaseBase}/go-delve/delve/releases/download/${DELVE_VERSION}/${asset.archiveName}`;
|
|
2437
|
+
const archivePath = path12.join(
|
|
2438
|
+
adaptersDir,
|
|
2439
|
+
`.delve.archive.${process.pid}.${randomBytes3(4).toString("hex")}.${asset.archiveKind === "zip" ? "zip" : "tar.gz"}`
|
|
2440
|
+
);
|
|
2441
|
+
try {
|
|
2442
|
+
await fs9.mkdir(adaptersDir, { recursive: true });
|
|
2443
|
+
await downloadToFile({ url, destPath: archivePath, env });
|
|
2444
|
+
const actualSha = await computeSha256(archivePath);
|
|
2445
|
+
if (actualSha !== expectedSha) {
|
|
2446
|
+
throw usageError("Delve archive failed SHA-256 verification.", {
|
|
2447
|
+
code: "provision_checksum_mismatch",
|
|
2448
|
+
diagnostics: [
|
|
2449
|
+
`URL: ${url}`,
|
|
2450
|
+
`Expected: ${expectedSha}`,
|
|
2451
|
+
`Actual: ${actualSha}`,
|
|
2452
|
+
"Re-run setup or report at https://github.com/roblourens/dap-cli/issues if persistent."
|
|
2453
|
+
],
|
|
2454
|
+
data: {
|
|
2455
|
+
adapterId: "delve",
|
|
2456
|
+
version: DELVE_VERSION,
|
|
2457
|
+
url,
|
|
2458
|
+
expectedSha,
|
|
2459
|
+
actualSha
|
|
2460
|
+
}
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
await atomicInstall({
|
|
2464
|
+
adaptersDir,
|
|
2465
|
+
adapterId: "delve",
|
|
2466
|
+
expectedEntrypoints: [asset.executableName],
|
|
2467
|
+
populate: async (stagingDir) => {
|
|
2468
|
+
if (asset.archiveKind === "zip") {
|
|
2469
|
+
await extractZip(archivePath, stagingDir);
|
|
2470
|
+
} else {
|
|
2471
|
+
await extractTarGz(archivePath, stagingDir);
|
|
2472
|
+
}
|
|
2473
|
+
if (process.platform !== "win32") {
|
|
2474
|
+
await fs9.chmod(path12.join(stagingDir, asset.executableName), 493);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
});
|
|
2478
|
+
await writeConsentMarker(adaptersDir, "delve", DELVE_VERSION);
|
|
2479
|
+
} finally {
|
|
2480
|
+
await fs9.rm(archivePath, { force: true }).catch(() => void 0);
|
|
2481
|
+
}
|
|
2482
|
+
});
|
|
2483
|
+
return {
|
|
2484
|
+
adapterId: "delve",
|
|
2485
|
+
version: DELVE_VERSION,
|
|
2486
|
+
installRoot,
|
|
2487
|
+
entrypoint,
|
|
2488
|
+
fromCache: false
|
|
2489
|
+
};
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
// src/adapters/provision/codelldb.ts
|
|
2493
|
+
import path13 from "path";
|
|
2494
|
+
import { promises as fs10 } from "fs";
|
|
2495
|
+
import { createHash as createHash4, randomBytes as randomBytes4 } from "crypto";
|
|
2496
|
+
var ADAPTER_ID2 = "codelldb";
|
|
2497
|
+
var DEFAULT_RELEASE_BASE_URL2 = "https://github.com";
|
|
2498
|
+
var ENTRYPOINT = "extension/adapter/codelldb";
|
|
2499
|
+
var REQUIRED_RUNTIME_PATHS = [
|
|
2500
|
+
ENTRYPOINT,
|
|
2501
|
+
"extension/adapter/scripts/codelldb/__init__.py",
|
|
2502
|
+
"extension/lldb/bin/lldb",
|
|
2503
|
+
"extension/lldb/bin/lldb-argdumper",
|
|
2504
|
+
"extension/lldb/bin/lldb-server",
|
|
2505
|
+
"extension/lldb/lib/liblldb.dylib",
|
|
2506
|
+
"extension/lldb/lib/libpython312.dylib",
|
|
2507
|
+
"extension/lldb/lib/python3.12/os.py",
|
|
2508
|
+
"extension/lang_support/rust.py",
|
|
2509
|
+
"extension/package.json"
|
|
2510
|
+
];
|
|
2511
|
+
var EXECUTABLE_PATHS = [
|
|
2512
|
+
ENTRYPOINT,
|
|
2513
|
+
"extension/lldb/bin/lldb",
|
|
2514
|
+
"extension/lldb/bin/lldb-argdumper",
|
|
2515
|
+
"extension/lldb/bin/lldb-server"
|
|
2516
|
+
];
|
|
2517
|
+
function resolveCodeLldbAsset(env) {
|
|
2518
|
+
const override = env.DAP_CLI_PROVISION_CODELLDB_PLATFORM_OVERRIDE;
|
|
2519
|
+
const detected = override !== void 0 && override.length > 0 ? override : `${process.platform}_${process.arch}`;
|
|
2520
|
+
if (detected !== "darwin_arm64") {
|
|
2521
|
+
throw usageError(`CodeLLDB provisioning does not support platform '${detected}'.`, {
|
|
2522
|
+
code: "provision_arch_unsupported",
|
|
2523
|
+
diagnostics: [
|
|
2524
|
+
`Detected platform: ${detected}`,
|
|
2525
|
+
"Supported platforms: darwin_arm64.",
|
|
2526
|
+
"Only the official CodeLLDB darwin-arm64 artifact has passed verification."
|
|
2527
|
+
],
|
|
2528
|
+
data: {
|
|
2529
|
+
adapterId: ADAPTER_ID2,
|
|
2530
|
+
detected,
|
|
2531
|
+
supported: ["darwin_arm64"]
|
|
2532
|
+
}
|
|
1391
2533
|
});
|
|
1392
|
-
throw error;
|
|
1393
2534
|
}
|
|
2535
|
+
return {
|
|
2536
|
+
platformKey: "darwin_arm64",
|
|
2537
|
+
archiveName: "codelldb-darwin-arm64.vsix"
|
|
2538
|
+
};
|
|
1394
2539
|
}
|
|
1395
|
-
async function
|
|
1396
|
-
const
|
|
1397
|
-
let lastError;
|
|
1398
|
-
while (Date.now() < deadline) {
|
|
2540
|
+
async function isCodeLldbRuntimeReady(installRoot) {
|
|
2541
|
+
for (const relativePath of REQUIRED_RUNTIME_PATHS) {
|
|
1399
2542
|
try {
|
|
1400
|
-
|
|
1401
|
-
} catch
|
|
1402
|
-
|
|
1403
|
-
await delay(50);
|
|
2543
|
+
await fs10.access(path13.join(installRoot, relativePath));
|
|
2544
|
+
} catch {
|
|
2545
|
+
return false;
|
|
1404
2546
|
}
|
|
1405
2547
|
}
|
|
1406
|
-
|
|
2548
|
+
return true;
|
|
1407
2549
|
}
|
|
1408
|
-
function
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
2550
|
+
async function computeSha2562(filePath) {
|
|
2551
|
+
const buffer = await fs10.readFile(filePath);
|
|
2552
|
+
return createHash4("sha256").update(buffer).digest("hex");
|
|
2553
|
+
}
|
|
2554
|
+
async function provisionCodeLldb(ctx) {
|
|
2555
|
+
const asset = resolveCodeLldbAsset(ctx.env);
|
|
2556
|
+
const installRoot = path13.join(ctx.adaptersDir, ADAPTER_ID2);
|
|
2557
|
+
const entrypoint = path13.join(installRoot, ENTRYPOINT);
|
|
2558
|
+
const expectedSha = CODELLDB_CHECKSUMS[CODELLDB_VERSION]?.[asset.platformKey];
|
|
2559
|
+
if (expectedSha === void 0) {
|
|
2560
|
+
throw usageError(`No pinned SHA-256 for CodeLLDB ${CODELLDB_VERSION} on ${asset.platformKey}.`, {
|
|
2561
|
+
code: "provision_checksum_mismatch",
|
|
2562
|
+
diagnostics: [
|
|
2563
|
+
`Adapter: ${ADAPTER_ID2} ${CODELLDB_VERSION} (${asset.platformKey})`,
|
|
2564
|
+
"src/adapters/provision/checksums.ts must list a verified checksum for every supported platform.",
|
|
2565
|
+
"Re-run setup or report at https://github.com/roblourens/dap-cli/issues if persistent."
|
|
2566
|
+
],
|
|
2567
|
+
data: { adapterId: ADAPTER_ID2, version: CODELLDB_VERSION, platform: asset.platformKey }
|
|
1421
2568
|
});
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
|
-
async function terminateChild2(child) {
|
|
1425
|
-
if (child.exitCode !== null || child.signalCode !== null) {
|
|
1426
|
-
return;
|
|
1427
|
-
}
|
|
1428
|
-
child.kill("SIGTERM");
|
|
1429
|
-
if (await waitForExit2(child, 100)) {
|
|
1430
|
-
return;
|
|
1431
2569
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
}
|
|
1435
|
-
function waitForExit2(child, timeoutMs) {
|
|
1436
|
-
if (child.exitCode !== null || child.signalCode !== null) {
|
|
1437
|
-
return Promise.resolve(true);
|
|
2570
|
+
if (await hasConsentMarker(ctx.adaptersDir, ADAPTER_ID2, CODELLDB_VERSION) && await isCodeLldbRuntimeReady(installRoot)) {
|
|
2571
|
+
return { adapterId: ADAPTER_ID2, version: CODELLDB_VERSION, installRoot, entrypoint, fromCache: true };
|
|
1438
2572
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
}
|
|
1448
|
-
child.once("exit", onExit);
|
|
2573
|
+
await confirm({
|
|
2574
|
+
assumeYes: ctx.assumeYes,
|
|
2575
|
+
question: `Install CodeLLDB ${CODELLDB_VERSION} into ${installRoot}/ (~44MB download)?`,
|
|
2576
|
+
details: [
|
|
2577
|
+
`Downloads the official release asset ${asset.archiveName} from github.com/vadimcn/codelldb.`,
|
|
2578
|
+
"The full VSIX runtime tree is cached locally after SHA-256 verification."
|
|
2579
|
+
],
|
|
2580
|
+
...ctx.stdin === void 0 ? {} : { stdin: ctx.stdin },
|
|
2581
|
+
...ctx.stderr === void 0 ? {} : { stderr: ctx.stderr }
|
|
1449
2582
|
});
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
stderrTail.push(line);
|
|
1454
|
-
while (stderrTail.length > 100) {
|
|
1455
|
-
stderrTail.shift();
|
|
2583
|
+
await withAdapterLock(ctx.adaptersDir, ADAPTER_ID2, async () => {
|
|
2584
|
+
if (await hasConsentMarker(ctx.adaptersDir, ADAPTER_ID2, CODELLDB_VERSION) && await isCodeLldbRuntimeReady(installRoot)) {
|
|
2585
|
+
return;
|
|
1456
2586
|
}
|
|
1457
|
-
|
|
2587
|
+
const releaseBase = ctx.env.DAP_CLI_PROVISION_RELEASE_BASE_URL ?? DEFAULT_RELEASE_BASE_URL2;
|
|
2588
|
+
const url = `${releaseBase}/vadimcn/codelldb/releases/download/${CODELLDB_VERSION}/${asset.archiveName}`;
|
|
2589
|
+
const archivePath = path13.join(ctx.adaptersDir, `.${ADAPTER_ID2}.archive.${process.pid}.${randomBytes4(4).toString("hex")}.vsix`);
|
|
2590
|
+
try {
|
|
2591
|
+
await fs10.mkdir(ctx.adaptersDir, { recursive: true });
|
|
2592
|
+
await downloadToFile({ url, destPath: archivePath, env: ctx.env });
|
|
2593
|
+
const actualSha = await computeSha2562(archivePath);
|
|
2594
|
+
if (actualSha !== expectedSha) {
|
|
2595
|
+
throw usageError("CodeLLDB archive failed SHA-256 verification.", {
|
|
2596
|
+
code: "provision_checksum_mismatch",
|
|
2597
|
+
diagnostics: [
|
|
2598
|
+
`URL: ${url}`,
|
|
2599
|
+
`Expected: ${expectedSha}`,
|
|
2600
|
+
`Actual: ${actualSha}`,
|
|
2601
|
+
"Re-run setup or report at https://github.com/roblourens/dap-cli/issues if persistent."
|
|
2602
|
+
],
|
|
2603
|
+
data: { adapterId: ADAPTER_ID2, version: CODELLDB_VERSION, url, expectedSha, actualSha }
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
await atomicInstall({
|
|
2607
|
+
adaptersDir: ctx.adaptersDir,
|
|
2608
|
+
adapterId: ADAPTER_ID2,
|
|
2609
|
+
expectedEntrypoints: REQUIRED_RUNTIME_PATHS,
|
|
2610
|
+
populate: async (stagingDir) => {
|
|
2611
|
+
await extractZip(archivePath, stagingDir);
|
|
2612
|
+
for (const executablePath of EXECUTABLE_PATHS) {
|
|
2613
|
+
await fs10.chmod(path13.join(stagingDir, executablePath), 493);
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
await writeConsentMarker(ctx.adaptersDir, ADAPTER_ID2, CODELLDB_VERSION);
|
|
2618
|
+
} finally {
|
|
2619
|
+
await fs10.rm(archivePath, { force: true }).catch(() => void 0);
|
|
2620
|
+
}
|
|
2621
|
+
});
|
|
2622
|
+
return { adapterId: ADAPTER_ID2, version: CODELLDB_VERSION, installRoot, entrypoint, fromCache: false };
|
|
1458
2623
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
2624
|
+
|
|
2625
|
+
// src/adapters/provision/index.ts
|
|
2626
|
+
async function provisionAdapter(id, ctx) {
|
|
2627
|
+
switch (id) {
|
|
2628
|
+
case "js-debug":
|
|
2629
|
+
return provisionJsDebug(ctx);
|
|
2630
|
+
case "debugpy":
|
|
2631
|
+
return provisionDebugpy(ctx);
|
|
2632
|
+
case "delve":
|
|
2633
|
+
return provisionDelve(ctx);
|
|
2634
|
+
case "codelldb":
|
|
2635
|
+
return provisionCodeLldb(ctx);
|
|
2636
|
+
}
|
|
1461
2637
|
}
|
|
1462
2638
|
|
|
1463
2639
|
// src/adapters/builtins/jsDebug.ts
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
function createJsDebugDescriptor(jsDebugPath) {
|
|
1467
|
-
const dapServerPath = jsDebugPath ?? resolveDefaultJsDebugPath();
|
|
2640
|
+
async function createJsDebugDescriptor(jsDebugPath) {
|
|
2641
|
+
const dapServerPath = jsDebugPath ?? await resolveDefaultJsDebugPath();
|
|
1468
2642
|
return {
|
|
1469
2643
|
id: "js-debug",
|
|
1470
2644
|
label: "JavaScript Debug Adapter (Node, Chrome, Electron)",
|
|
@@ -1488,26 +2662,35 @@ function applyJsDebugTraceDefaults(config, logDir) {
|
|
|
1488
2662
|
...record,
|
|
1489
2663
|
trace: {
|
|
1490
2664
|
stdio: false,
|
|
1491
|
-
logFile:
|
|
2665
|
+
logFile: path14.join(logDir, `js-debug-trace-${Date.now()}.log`)
|
|
1492
2666
|
}
|
|
1493
2667
|
};
|
|
1494
2668
|
}
|
|
1495
|
-
function
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
if (found !== void 0) {
|
|
1502
|
-
return found;
|
|
2669
|
+
async function pathExists(candidate) {
|
|
2670
|
+
try {
|
|
2671
|
+
await fs11.access(candidate);
|
|
2672
|
+
return true;
|
|
2673
|
+
} catch {
|
|
2674
|
+
return false;
|
|
1503
2675
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
2676
|
+
}
|
|
2677
|
+
async function resolveDefaultJsDebugPath() {
|
|
2678
|
+
const env = process.env;
|
|
2679
|
+
const adaptersDir = getDapCliAdaptersDir(env);
|
|
2680
|
+
const provisionedEntrypoint = path14.join(adaptersDir, "js-debug", "src", "dapDebugServer.js");
|
|
2681
|
+
const repoEntrypoint = path14.join(process.cwd(), "node_modules", "vscode-js-debug", "src", "dapDebugServer.js");
|
|
2682
|
+
if (await pathExists(provisionedEntrypoint)) {
|
|
2683
|
+
return provisionedEntrypoint;
|
|
2684
|
+
}
|
|
2685
|
+
if (await pathExists(repoEntrypoint)) {
|
|
2686
|
+
return repoEntrypoint;
|
|
2687
|
+
}
|
|
2688
|
+
const result = await provisionAdapter("js-debug", {
|
|
2689
|
+
env,
|
|
2690
|
+
assumeYes: resolveAssumeYes(void 0, env),
|
|
2691
|
+
adaptersDir
|
|
1510
2692
|
});
|
|
2693
|
+
return result.entrypoint;
|
|
1511
2694
|
}
|
|
1512
2695
|
|
|
1513
2696
|
// src/protocol/dapClient.ts
|
|
@@ -1601,14 +2784,16 @@ function parseBody(body) {
|
|
|
1601
2784
|
|
|
1602
2785
|
// src/protocol/dapClient.ts
|
|
1603
2786
|
var DapResponseError = class extends Error {
|
|
1604
|
-
constructor(command, requestSeq, message) {
|
|
2787
|
+
constructor(command, requestSeq, message, responseBody) {
|
|
1605
2788
|
super(message);
|
|
1606
2789
|
this.command = command;
|
|
1607
2790
|
this.requestSeq = requestSeq;
|
|
2791
|
+
this.responseBody = responseBody;
|
|
1608
2792
|
this.name = "DapResponseError";
|
|
1609
2793
|
}
|
|
1610
2794
|
command;
|
|
1611
2795
|
requestSeq;
|
|
2796
|
+
responseBody;
|
|
1612
2797
|
};
|
|
1613
2798
|
var DapTransportClosedError = class extends Error {
|
|
1614
2799
|
constructor(message = "DAP transport closed.") {
|
|
@@ -1629,6 +2814,9 @@ var DapClient = class {
|
|
|
1629
2814
|
transport.readable.on("close", this.handleClosed);
|
|
1630
2815
|
transport.readable.on("end", this.handleClosed);
|
|
1631
2816
|
transport.readable.on("error", this.handleTransportError);
|
|
2817
|
+
if (!Object.is(transport.writable, transport.readable)) {
|
|
2818
|
+
transport.writable.on("error", this.handleTransportError);
|
|
2819
|
+
}
|
|
1632
2820
|
}
|
|
1633
2821
|
transport;
|
|
1634
2822
|
options;
|
|
@@ -1691,9 +2879,12 @@ var DapClient = class {
|
|
|
1691
2879
|
}
|
|
1692
2880
|
async close() {
|
|
1693
2881
|
this.handleClosed();
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
2882
|
+
try {
|
|
2883
|
+
await this.terminateChildProcesses();
|
|
2884
|
+
await this.transport.close();
|
|
2885
|
+
} finally {
|
|
2886
|
+
this.detachTransportHandlers();
|
|
2887
|
+
}
|
|
1697
2888
|
}
|
|
1698
2889
|
handleData = (chunk) => {
|
|
1699
2890
|
try {
|
|
@@ -1713,7 +2904,8 @@ var DapClient = class {
|
|
|
1713
2904
|
this.rejectPending(new DapTransportClosedError());
|
|
1714
2905
|
};
|
|
1715
2906
|
handleTransportError = (error) => {
|
|
1716
|
-
this.
|
|
2907
|
+
this.closed = true;
|
|
2908
|
+
this.rejectPending(error instanceof DapTransportClosedError ? error : new DapTransportClosedError(error.message));
|
|
1717
2909
|
};
|
|
1718
2910
|
handleMessage(message) {
|
|
1719
2911
|
if (message.type === "event") {
|
|
@@ -1736,7 +2928,7 @@ var DapClient = class {
|
|
|
1736
2928
|
clearTimeout(pending.timeout);
|
|
1737
2929
|
}
|
|
1738
2930
|
if (!response.success) {
|
|
1739
|
-
pending.reject(new DapResponseError(response.command, response.request_seq, response.message ?? `DAP request failed: ${response.command}
|
|
2931
|
+
pending.reject(new DapResponseError(response.command, response.request_seq, response.message ?? `DAP request failed: ${response.command}`, response.body));
|
|
1740
2932
|
return;
|
|
1741
2933
|
}
|
|
1742
2934
|
pending.resolve(response.body);
|
|
@@ -1818,6 +3010,9 @@ var DapClient = class {
|
|
|
1818
3010
|
this.transport.readable.removeListener("close", this.handleClosed);
|
|
1819
3011
|
this.transport.readable.removeListener("end", this.handleClosed);
|
|
1820
3012
|
this.transport.readable.removeListener("error", this.handleTransportError);
|
|
3013
|
+
if (!Object.is(this.transport.writable, this.transport.readable)) {
|
|
3014
|
+
this.transport.writable.removeListener("error", this.handleTransportError);
|
|
3015
|
+
}
|
|
1821
3016
|
}
|
|
1822
3017
|
handleRunInTerminal(argumentsValue) {
|
|
1823
3018
|
const parsed = runInTerminalArgumentsSchema.safeParse(argumentsValue);
|
|
@@ -2149,14 +3344,14 @@ function getDapGeneratedCommand(command) {
|
|
|
2149
3344
|
}
|
|
2150
3345
|
|
|
2151
3346
|
// src/sessions/session.ts
|
|
2152
|
-
import { randomBytes } from "crypto";
|
|
3347
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
2153
3348
|
var REMOVABLE_LIFECYCLES = /* @__PURE__ */ new Set([
|
|
2154
3349
|
"terminated",
|
|
2155
3350
|
"disconnected",
|
|
2156
3351
|
"failed"
|
|
2157
3352
|
]);
|
|
2158
3353
|
function createSessionId() {
|
|
2159
|
-
return `sess_${
|
|
3354
|
+
return `sess_${randomBytes5(12).toString("base64url")}`;
|
|
2160
3355
|
}
|
|
2161
3356
|
function projectSessionSummary(session) {
|
|
2162
3357
|
const summary = {
|
|
@@ -2269,9 +3464,9 @@ function createAmbiguousSessionDiagnostics(targetId, matches) {
|
|
|
2269
3464
|
}
|
|
2270
3465
|
|
|
2271
3466
|
// src/sessions/sessionStore.ts
|
|
2272
|
-
import { promises as
|
|
3467
|
+
import { promises as fs12 } from "fs";
|
|
2273
3468
|
import { randomUUID } from "crypto";
|
|
2274
|
-
import
|
|
3469
|
+
import path15 from "path";
|
|
2275
3470
|
import { z as z5 } from "zod";
|
|
2276
3471
|
var ownedAdapterSchema = z5.object({
|
|
2277
3472
|
pid: z5.number().int().positive().optional(),
|
|
@@ -2313,14 +3508,14 @@ var SessionStore = class {
|
|
|
2313
3508
|
storePath;
|
|
2314
3509
|
constructor(options = {}) {
|
|
2315
3510
|
const env = options.dapCliHome === void 0 ? process.env : { ...process.env, DAP_CLI_HOME: options.dapCliHome };
|
|
2316
|
-
this.storePath =
|
|
3511
|
+
this.storePath = path15.join(getDapCliStateDir(env), "sessions.json");
|
|
2317
3512
|
}
|
|
2318
3513
|
get path() {
|
|
2319
3514
|
return this.storePath;
|
|
2320
3515
|
}
|
|
2321
3516
|
async read() {
|
|
2322
3517
|
try {
|
|
2323
|
-
const raw = await
|
|
3518
|
+
const raw = await fs12.readFile(this.storePath, "utf8");
|
|
2324
3519
|
const parsed = sessionStoreSchema.parse(JSON.parse(raw));
|
|
2325
3520
|
return {
|
|
2326
3521
|
...parsed.activeSessionId !== void 0 ? { activeSessionId: parsed.activeSessionId } : {},
|
|
@@ -2333,7 +3528,7 @@ var SessionStore = class {
|
|
|
2333
3528
|
if (error instanceof SyntaxError || error instanceof z5.ZodError) {
|
|
2334
3529
|
const backupPath = `${this.storePath}.corrupt.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
|
|
2335
3530
|
try {
|
|
2336
|
-
await
|
|
3531
|
+
await fs12.rename(this.storePath, backupPath);
|
|
2337
3532
|
process.stderr.write(`dap-cli: sessions.json was unparseable; moved to ${backupPath} and continuing with empty state.
|
|
2338
3533
|
`);
|
|
2339
3534
|
} catch {
|
|
@@ -2344,12 +3539,12 @@ var SessionStore = class {
|
|
|
2344
3539
|
}
|
|
2345
3540
|
}
|
|
2346
3541
|
async write(data) {
|
|
2347
|
-
await
|
|
3542
|
+
await fs12.mkdir(path15.dirname(this.storePath), { recursive: true });
|
|
2348
3543
|
const validated = sessionStoreSchema.parse(data);
|
|
2349
3544
|
const tempPath = `${this.storePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
2350
|
-
await
|
|
3545
|
+
await fs12.writeFile(tempPath, `${JSON.stringify(validated, null, 2)}
|
|
2351
3546
|
`, "utf8");
|
|
2352
|
-
await
|
|
3547
|
+
await fs12.rename(tempPath, this.storePath);
|
|
2353
3548
|
}
|
|
2354
3549
|
};
|
|
2355
3550
|
function isNodeError2(error) {
|
|
@@ -3082,22 +4277,72 @@ var ChildSessionCoordinator = class {
|
|
|
3082
4277
|
try {
|
|
3083
4278
|
await this.options.sessionManager.updateLifecycle(childId, command === "launch" ? "launching" : "attaching").catch(() => void 0);
|
|
3084
4279
|
const requestPromise = client.request(command, config);
|
|
3085
|
-
|
|
4280
|
+
let launchAttachOutcome = "pending";
|
|
4281
|
+
requestPromise.then(
|
|
4282
|
+
() => {
|
|
4283
|
+
launchAttachOutcome = "fulfilled";
|
|
4284
|
+
},
|
|
4285
|
+
(error) => {
|
|
4286
|
+
launchAttachOutcome = { error };
|
|
4287
|
+
}
|
|
4288
|
+
);
|
|
3086
4289
|
await runtime.initializedPromise;
|
|
3087
4290
|
for (const breakpointArgs of this.pendingSetBreakpoints) {
|
|
3088
4291
|
await client.request("setBreakpoints", breakpointArgs);
|
|
3089
4292
|
}
|
|
3090
4293
|
await client.request("configurationDone");
|
|
3091
|
-
await
|
|
4294
|
+
await Promise.resolve();
|
|
4295
|
+
const getLaunchAttachOutcome = () => launchAttachOutcome;
|
|
4296
|
+
const outcomeAfterConfiguration = getLaunchAttachOutcome();
|
|
4297
|
+
if (typeof outcomeAfterConfiguration === "object") {
|
|
4298
|
+
throw outcomeAfterConfiguration.error;
|
|
4299
|
+
}
|
|
3092
4300
|
await this.options.sessionManager.updateLifecycle(childId, "running").catch(() => void 0);
|
|
3093
4301
|
if (!runtime.readySeen) {
|
|
3094
4302
|
runtime.readySeen = true;
|
|
3095
4303
|
runtime.resolveReady();
|
|
3096
4304
|
}
|
|
4305
|
+
if (outcomeAfterConfiguration === "pending") {
|
|
4306
|
+
this.observeTrailingLaunchAttachResponse(childId, command, requestPromise);
|
|
4307
|
+
}
|
|
3097
4308
|
} catch (error) {
|
|
3098
4309
|
await this.markChildFailed(childId, error);
|
|
3099
4310
|
}
|
|
3100
4311
|
}
|
|
4312
|
+
/**
|
|
4313
|
+
* Observe a launch/attach response that arrives — or times out — AFTER the
|
|
4314
|
+
* child has already been marked `running` off the back of `configurationDone`.
|
|
4315
|
+
*
|
|
4316
|
+
* A trailing rejection no longer fails the child: the session is configured
|
|
4317
|
+
* and usable, and gating readiness on this response wedged js-debug page
|
|
4318
|
+
* sessions (see {@link runChildLifecycle}). Instead it is surfaced as a
|
|
4319
|
+
* non-fatal `output` warning on the parent so the condition stays debuggable
|
|
4320
|
+
* from the agent side. Transport-closed rejections during normal teardown are
|
|
4321
|
+
* ignored — they are not an attach anomaly.
|
|
4322
|
+
*/
|
|
4323
|
+
observeTrailingLaunchAttachResponse(childId, command, requestPromise) {
|
|
4324
|
+
void requestPromise.then(
|
|
4325
|
+
() => void 0,
|
|
4326
|
+
(error) => {
|
|
4327
|
+
if (error instanceof DapTransportClosedError) {
|
|
4328
|
+
return;
|
|
4329
|
+
}
|
|
4330
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4331
|
+
const synthetic = {
|
|
4332
|
+
seq: 0,
|
|
4333
|
+
type: "event",
|
|
4334
|
+
event: "output",
|
|
4335
|
+
body: {
|
|
4336
|
+
category: "stderr",
|
|
4337
|
+
output: `child session ${childId} ${command} response not received: ${message}
|
|
4338
|
+
`,
|
|
4339
|
+
child_session_id: childId
|
|
4340
|
+
}
|
|
4341
|
+
};
|
|
4342
|
+
this.options.parentEventCache.append(this.options.parentSessionId, synthetic);
|
|
4343
|
+
}
|
|
4344
|
+
);
|
|
4345
|
+
}
|
|
3101
4346
|
async markChildFailed(childId, error) {
|
|
3102
4347
|
await this.options.sessionManager.updateLifecycle(childId, "failed").catch(() => void 0);
|
|
3103
4348
|
const runtime = this.children.get(childId);
|
|
@@ -3755,12 +5000,12 @@ function sourceMatches(eventPath, requestPath) {
|
|
|
3755
5000
|
if (eventPath === requestPath) {
|
|
3756
5001
|
return true;
|
|
3757
5002
|
}
|
|
3758
|
-
const normalize = (
|
|
5003
|
+
const normalize = (path26) => path26.replace(/^file:\/\//, "").replace(/\\/g, "/");
|
|
3759
5004
|
return normalize(eventPath) === normalize(requestPath);
|
|
3760
5005
|
}
|
|
3761
5006
|
|
|
3762
5007
|
// src/sessions/helperProcessDetection.ts
|
|
3763
|
-
import { execFile } from "child_process";
|
|
5008
|
+
import { execFile as execFile2 } from "child_process";
|
|
3764
5009
|
var helperProcessWarningEventName = "dapCli.helperProcessWarning";
|
|
3765
5010
|
var helperHint = "Likely attached to an adapter-spawned helper process; you probably meant `dap-cli attach`, not `dap-cli launch`.";
|
|
3766
5011
|
function createHelperProcessDetector(options) {
|
|
@@ -3839,7 +5084,7 @@ function defaultLookupPpid(pid) {
|
|
|
3839
5084
|
return Promise.resolve(void 0);
|
|
3840
5085
|
}
|
|
3841
5086
|
return new Promise((resolve) => {
|
|
3842
|
-
|
|
5087
|
+
execFile2("ps", ["-o", "ppid=", "-p", String(pid)], { timeout: psLookupTimeoutMs }, (error, stdout) => {
|
|
3843
5088
|
if (error !== null) {
|
|
3844
5089
|
resolve(void 0);
|
|
3845
5090
|
return;
|
|
@@ -3860,8 +5105,8 @@ function defaultLookupPpid(pid) {
|
|
|
3860
5105
|
}
|
|
3861
5106
|
|
|
3862
5107
|
// src/controller/buildId.ts
|
|
3863
|
-
import { promises as
|
|
3864
|
-
import
|
|
5108
|
+
import { promises as fs13 } from "fs";
|
|
5109
|
+
import path16 from "path";
|
|
3865
5110
|
import { fileURLToPath } from "url";
|
|
3866
5111
|
var cachedBuildId;
|
|
3867
5112
|
async function computeBuildId() {
|
|
@@ -3873,14 +5118,14 @@ async function computeBuildId() {
|
|
|
3873
5118
|
cachedBuildId = envOverride;
|
|
3874
5119
|
return cachedBuildId;
|
|
3875
5120
|
}
|
|
3876
|
-
const here =
|
|
5121
|
+
const here = path16.dirname(fileURLToPath(import.meta.url));
|
|
3877
5122
|
const pkgPath = await findPackageJson(here);
|
|
3878
5123
|
let version = "0.0.0";
|
|
3879
5124
|
let pkgDir = here;
|
|
3880
5125
|
if (pkgPath !== void 0) {
|
|
3881
|
-
pkgDir =
|
|
5126
|
+
pkgDir = path16.dirname(pkgPath);
|
|
3882
5127
|
try {
|
|
3883
|
-
const raw = await
|
|
5128
|
+
const raw = await fs13.readFile(pkgPath, "utf8");
|
|
3884
5129
|
const parsed = JSON.parse(raw);
|
|
3885
5130
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
3886
5131
|
version = parsed.version;
|
|
@@ -3888,9 +5133,9 @@ async function computeBuildId() {
|
|
|
3888
5133
|
} catch {
|
|
3889
5134
|
}
|
|
3890
5135
|
}
|
|
3891
|
-
const distPath =
|
|
5136
|
+
const distPath = path16.join(pkgDir, "dist", "index.js");
|
|
3892
5137
|
try {
|
|
3893
|
-
const stat = await
|
|
5138
|
+
const stat = await fs13.stat(distPath);
|
|
3894
5139
|
cachedBuildId = `${version}:dist:${stat.mtimeMs}:${stat.size}`;
|
|
3895
5140
|
} catch {
|
|
3896
5141
|
cachedBuildId = `${version}:src`;
|
|
@@ -3900,12 +5145,12 @@ async function computeBuildId() {
|
|
|
3900
5145
|
async function findPackageJson(startDir) {
|
|
3901
5146
|
let dir = startDir;
|
|
3902
5147
|
for (let depth = 0; depth < 8; depth += 1) {
|
|
3903
|
-
const candidate =
|
|
5148
|
+
const candidate = path16.join(dir, "package.json");
|
|
3904
5149
|
try {
|
|
3905
|
-
await
|
|
5150
|
+
await fs13.access(candidate);
|
|
3906
5151
|
return candidate;
|
|
3907
5152
|
} catch {
|
|
3908
|
-
const parent =
|
|
5153
|
+
const parent = path16.dirname(dir);
|
|
3909
5154
|
if (parent === dir) {
|
|
3910
5155
|
return void 0;
|
|
3911
5156
|
}
|
|
@@ -4271,12 +5516,12 @@ var ControllerServer = class {
|
|
|
4271
5516
|
}
|
|
4272
5517
|
if (request.method === "sessions.stop") {
|
|
4273
5518
|
const target = getOptionalStringParam(request.params, "name");
|
|
4274
|
-
await this.disconnectRuntimeForTarget(target);
|
|
5519
|
+
await this.disconnectRuntimeForTarget(target, { terminateDebuggee: true });
|
|
4275
5520
|
return manager.stopSession(target);
|
|
4276
5521
|
}
|
|
4277
5522
|
if (request.method === "sessions.detach") {
|
|
4278
5523
|
const target = getOptionalStringParam(request.params, "name");
|
|
4279
|
-
await this.disconnectRuntimeForTarget(target);
|
|
5524
|
+
await this.disconnectRuntimeForTarget(target, { terminateDebuggee: false });
|
|
4280
5525
|
return manager.detachSession(target);
|
|
4281
5526
|
}
|
|
4282
5527
|
if (request.method === "sessions.close") {
|
|
@@ -4658,7 +5903,7 @@ var ControllerServer = class {
|
|
|
4658
5903
|
return { sources: [] };
|
|
4659
5904
|
}
|
|
4660
5905
|
if (source !== void 0) {
|
|
4661
|
-
const entry = map.get(
|
|
5906
|
+
const entry = map.get(path17.resolve(source));
|
|
4662
5907
|
return entry === void 0 ? { sources: [] } : { sources: [{ source: entry.source, breakpoints: entry.response, requested: entry.requested }] };
|
|
4663
5908
|
}
|
|
4664
5909
|
return {
|
|
@@ -4683,7 +5928,7 @@ var ControllerServer = class {
|
|
|
4683
5928
|
};
|
|
4684
5929
|
const cleared = [];
|
|
4685
5930
|
if (source !== void 0) {
|
|
4686
|
-
const key =
|
|
5931
|
+
const key = path17.resolve(source);
|
|
4687
5932
|
const entry = map.get(key);
|
|
4688
5933
|
if (entry === void 0) {
|
|
4689
5934
|
return { cleared: [] };
|
|
@@ -4835,7 +6080,7 @@ var ControllerServer = class {
|
|
|
4835
6080
|
}
|
|
4836
6081
|
});
|
|
4837
6082
|
}
|
|
4838
|
-
async disconnectRuntimeForTarget(target) {
|
|
6083
|
+
async disconnectRuntimeForTarget(target, opts) {
|
|
4839
6084
|
let status;
|
|
4840
6085
|
try {
|
|
4841
6086
|
status = this.requireSessionManager().status(target);
|
|
@@ -4849,7 +6094,9 @@ var ControllerServer = class {
|
|
|
4849
6094
|
if (runtime.children !== void 0) {
|
|
4850
6095
|
await runtime.children.dispose().catch(() => void 0);
|
|
4851
6096
|
}
|
|
4852
|
-
|
|
6097
|
+
const disconnect = runtime.lifecycle.disconnect({ terminateDebuggee: opts.terminateDebuggee });
|
|
6098
|
+
disconnect.catch(() => void 0);
|
|
6099
|
+
await this.waitForDisconnect(disconnect, controllerDisconnectTimeoutMs);
|
|
4853
6100
|
await runtime.client.close().catch(() => void 0);
|
|
4854
6101
|
await runtime.adapter.close().catch(() => void 0);
|
|
4855
6102
|
this.runtimes.delete(status.id);
|
|
@@ -5046,7 +6293,7 @@ function parseDapStartCompoundMemberParams(value) {
|
|
|
5046
6293
|
return parsed;
|
|
5047
6294
|
}
|
|
5048
6295
|
function createCompoundId() {
|
|
5049
|
-
return `compound_${
|
|
6296
|
+
return `compound_${randomBytes6(12).toString("base64url")}`;
|
|
5050
6297
|
}
|
|
5051
6298
|
function findFailedCompoundMemberName(error, members, startedMemberNames) {
|
|
5052
6299
|
const failed = members.find((member) => !startedMemberNames.includes(member.memberName));
|
|
@@ -5172,7 +6419,7 @@ function toDapCliError(error, context) {
|
|
|
5172
6419
|
}
|
|
5173
6420
|
return dapError(error.message, {
|
|
5174
6421
|
code: "dap_request_failed",
|
|
5175
|
-
diagnostics:
|
|
6422
|
+
diagnostics: createDapResponseDiagnostics(error),
|
|
5176
6423
|
sessionId: context.sessionId,
|
|
5177
6424
|
request: { command: error.command, seq: error.requestSeq },
|
|
5178
6425
|
adapter: context.adapter
|
|
@@ -5204,6 +6451,33 @@ function toDapCliError(error, context) {
|
|
|
5204
6451
|
diagnostics: ["The adapter failed while processing the DAP request. Check adapter stderr and log path."]
|
|
5205
6452
|
}, context));
|
|
5206
6453
|
}
|
|
6454
|
+
function createDapResponseDiagnostics(error) {
|
|
6455
|
+
const diagnostics = [`DAP request failed: ${error.command}. Inspect adapter diagnostics and session state.`];
|
|
6456
|
+
const responseDetail = extractDapResponseDetail(error.responseBody);
|
|
6457
|
+
if (responseDetail !== void 0 && responseDetail !== error.message) {
|
|
6458
|
+
diagnostics.push(`Adapter detail: ${responseDetail}`);
|
|
6459
|
+
}
|
|
6460
|
+
return diagnostics;
|
|
6461
|
+
}
|
|
6462
|
+
function extractDapResponseDetail(body) {
|
|
6463
|
+
if (!isRecord5(body)) {
|
|
6464
|
+
return void 0;
|
|
6465
|
+
}
|
|
6466
|
+
if (typeof body.message === "string" && body.message.trim().length > 0) {
|
|
6467
|
+
return body.message.trim();
|
|
6468
|
+
}
|
|
6469
|
+
const error = body.error;
|
|
6470
|
+
if (!isRecord5(error)) {
|
|
6471
|
+
return void 0;
|
|
6472
|
+
}
|
|
6473
|
+
if (typeof error.format === "string" && error.format.trim().length > 0) {
|
|
6474
|
+
return error.format.trim();
|
|
6475
|
+
}
|
|
6476
|
+
if (typeof error.message === "string" && error.message.trim().length > 0) {
|
|
6477
|
+
return error.message.trim();
|
|
6478
|
+
}
|
|
6479
|
+
return void 0;
|
|
6480
|
+
}
|
|
5207
6481
|
function toControllerErrorPayload(error) {
|
|
5208
6482
|
const payload = {
|
|
5209
6483
|
code: error.code,
|
|
@@ -5480,7 +6754,7 @@ async function delay2(ms) {
|
|
|
5480
6754
|
}
|
|
5481
6755
|
|
|
5482
6756
|
// src/cli/commands/dapAliases.ts
|
|
5483
|
-
import
|
|
6757
|
+
import path18 from "path";
|
|
5484
6758
|
|
|
5485
6759
|
// src/cli/commands/jsonOptions.ts
|
|
5486
6760
|
function parseJsonOption(value) {
|
|
@@ -5608,7 +6882,7 @@ function registerDapAliasCommands(program, output) {
|
|
|
5608
6882
|
breakpoints.command("set").requiredOption("--source <path>", "source path").requiredOption("--line <number...>", "breakpoint line").option("--name <name>", "session name or id").option("--condition <expr>", "breakpoint condition").option("--hit-condition <expr>", "breakpoint hit condition").option("--log-message <text>", "breakpoint log message").description("Set breakpoints at source file line numbers").action(async (options) => {
|
|
5609
6883
|
const lines = parseIntegerValues(options.line, "line");
|
|
5610
6884
|
const args = {
|
|
5611
|
-
source: { path:
|
|
6885
|
+
source: { path: path18.resolve(options.source) },
|
|
5612
6886
|
breakpoints: lines.map((line) => compactObject({
|
|
5613
6887
|
line,
|
|
5614
6888
|
condition: options.condition,
|
|
@@ -5641,7 +6915,7 @@ function registerDapAliasCommands(program, output) {
|
|
|
5641
6915
|
breakpoints.command("list").option("--source <path>", "filter to a single source path").option("--name <name>", "session name or id").description("List breakpoints currently tracked for a session (per source, with verified state)").action(async (options) => {
|
|
5642
6916
|
const params = compactObject({
|
|
5643
6917
|
name: options.name,
|
|
5644
|
-
source: options.source === void 0 ? void 0 :
|
|
6918
|
+
source: options.source === void 0 ? void 0 : path18.resolve(options.source)
|
|
5645
6919
|
});
|
|
5646
6920
|
const client = await createControllerClient({ dapCliHome: process.env.DAP_CLI_HOME });
|
|
5647
6921
|
try {
|
|
@@ -5654,7 +6928,7 @@ function registerDapAliasCommands(program, output) {
|
|
|
5654
6928
|
breakpoints.command("clear").option("--source <path>", "clear only the named source (otherwise: clear all tracked sources)").option("--name <name>", "session name or id").description("Clear breakpoints in a source (DAP setBreakpoints empty-list semantics)").action(async (options) => {
|
|
5655
6929
|
const params = compactObject({
|
|
5656
6930
|
name: options.name,
|
|
5657
|
-
source: options.source === void 0 ? void 0 :
|
|
6931
|
+
source: options.source === void 0 ? void 0 : path18.resolve(options.source)
|
|
5658
6932
|
});
|
|
5659
6933
|
const client = await createControllerClient({ dapCliHome: process.env.DAP_CLI_HOME });
|
|
5660
6934
|
try {
|
|
@@ -5682,10 +6956,10 @@ function registerDapAliasCommands(program, output) {
|
|
|
5682
6956
|
await sendAliasRequest(output, "variables", { variablesReference: parseRequiredIntegerOption(options.variablesReference, "variables-reference") }, options.name, "variables");
|
|
5683
6957
|
});
|
|
5684
6958
|
program.command("source").helpGroup("Paused-state inspection").requiredOption("--source-reference <number>", "source reference").option("--path <path>", "source path").option("--name <name>", "session name or id").description("Return source content").action(async (options) => {
|
|
5685
|
-
const
|
|
6959
|
+
const path26 = options.path;
|
|
5686
6960
|
await sendAliasRequest(output, "source", compactObject({
|
|
5687
6961
|
sourceReference: parseRequiredIntegerOption(options.sourceReference, "source-reference"),
|
|
5688
|
-
source:
|
|
6962
|
+
source: path26 === void 0 ? void 0 : { path: path26 }
|
|
5689
6963
|
}), options.name, "source");
|
|
5690
6964
|
});
|
|
5691
6965
|
program.command("evaluate").helpGroup("Paused-state inspection").requiredOption("--expression <expr>", "expression").option("--frame-id <number>", "frame id (auto-resolved to topmost paused frame when omitted)").option("--context <context>", "evaluation context").option("--name <name>", "session name or id").description("Evaluate an expression (auto-uses topmost frame of most-recently-stopped thread when paused)").action(async (options) => {
|
|
@@ -5942,9 +7216,9 @@ async function buildVerificationDiagnostic(client, requestedPath, breakpointsRes
|
|
|
5942
7216
|
}
|
|
5943
7217
|
const sources = loaded.sources ?? [];
|
|
5944
7218
|
const loadedSourcesCount = sources.length;
|
|
5945
|
-
const wantBasename =
|
|
7219
|
+
const wantBasename = path18.basename(requestedPath);
|
|
5946
7220
|
const cmp = process.platform === "win32" ? (a, b) => a.toLowerCase() === b.toLowerCase() : (a, b) => a === b;
|
|
5947
|
-
const matchingLoadedSources = sources.filter((s) => typeof s.path === "string" && (cmp(s.path, requestedPath) || cmp(
|
|
7221
|
+
const matchingLoadedSources = sources.filter((s) => typeof s.path === "string" && (cmp(s.path, requestedPath) || cmp(path18.basename(s.path), wantBasename))).map((s) => {
|
|
5948
7222
|
const sourcePath = s.path;
|
|
5949
7223
|
return typeof s.name === "string" ? { path: sourcePath, name: s.name } : { path: sourcePath };
|
|
5950
7224
|
});
|
|
@@ -5986,12 +7260,12 @@ async function countChildSessions(client, parentName) {
|
|
|
5986
7260
|
}
|
|
5987
7261
|
|
|
5988
7262
|
// src/cli/commands/dapCore.ts
|
|
5989
|
-
import
|
|
7263
|
+
import path24 from "path";
|
|
5990
7264
|
|
|
5991
7265
|
// src/adapters/config.ts
|
|
5992
|
-
import { promises as
|
|
7266
|
+
import { promises as fs14 } from "fs";
|
|
5993
7267
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
5994
|
-
import
|
|
7268
|
+
import path19 from "path";
|
|
5995
7269
|
import { z as z6 } from "zod";
|
|
5996
7270
|
var configuredAdapterDescriptorSchema = adapterDescriptorSchema.and(z6.object({
|
|
5997
7271
|
launchDefaults: z6.record(z6.string(), z6.unknown()).optional(),
|
|
@@ -6004,7 +7278,7 @@ var adapterConfigSchema = z6.object({
|
|
|
6004
7278
|
async function loadAdapterConfig(dapCliHome) {
|
|
6005
7279
|
const configPath = getAdapterConfigPath(dapCliHome);
|
|
6006
7280
|
try {
|
|
6007
|
-
const raw = await
|
|
7281
|
+
const raw = await fs14.readFile(configPath, "utf8");
|
|
6008
7282
|
return parseAdapterConfig(raw);
|
|
6009
7283
|
} catch (error) {
|
|
6010
7284
|
if (isNodeError4(error) && error.code === "ENOENT") {
|
|
@@ -6018,7 +7292,7 @@ async function loadAdapterConfig(dapCliHome) {
|
|
|
6018
7292
|
}
|
|
6019
7293
|
function getAdapterConfigPath(dapCliHome) {
|
|
6020
7294
|
const home = dapCliHome === void 0 ? getDapCliHome() : getDapCliHome({ ...process.env, DAP_CLI_HOME: dapCliHome });
|
|
6021
|
-
return
|
|
7295
|
+
return path19.join(home, "config", "adapters.json");
|
|
6022
7296
|
}
|
|
6023
7297
|
function parseAdapterConfig(raw) {
|
|
6024
7298
|
return adapterConfigSchema.parse(JSON.parse(raw));
|
|
@@ -6034,10 +7308,12 @@ function isNodeError4(error) {
|
|
|
6034
7308
|
}
|
|
6035
7309
|
|
|
6036
7310
|
// src/adapters/builtins/debugpy.ts
|
|
6037
|
-
import {
|
|
6038
|
-
import {
|
|
6039
|
-
|
|
6040
|
-
|
|
7311
|
+
import { promises as fs15 } from "fs";
|
|
7312
|
+
import { execFile as execFile3 } from "child_process";
|
|
7313
|
+
import { promisify as promisify2 } from "util";
|
|
7314
|
+
var execFileAsync2 = promisify2(execFile3);
|
|
7315
|
+
async function createDebugpyDescriptor(pythonPath) {
|
|
7316
|
+
const resolvedPythonPath = pythonPath ?? await resolveDefaultDebugpyPythonPath();
|
|
6041
7317
|
return {
|
|
6042
7318
|
id: "debugpy",
|
|
6043
7319
|
label: "Python Debug Adapter (debugpy)",
|
|
@@ -6048,26 +7324,158 @@ function createDebugpyDescriptor(pythonPath) {
|
|
|
6048
7324
|
}
|
|
6049
7325
|
};
|
|
6050
7326
|
}
|
|
6051
|
-
function
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
7327
|
+
async function pathExists2(p) {
|
|
7328
|
+
try {
|
|
7329
|
+
await fs15.access(p);
|
|
7330
|
+
return true;
|
|
7331
|
+
} catch {
|
|
7332
|
+
return false;
|
|
7333
|
+
}
|
|
7334
|
+
}
|
|
7335
|
+
async function pythonHasDebugpy(pythonPath) {
|
|
7336
|
+
try {
|
|
7337
|
+
await execFileAsync2(pythonPath, ["-c", "import debugpy"]);
|
|
7338
|
+
return true;
|
|
7339
|
+
} catch {
|
|
7340
|
+
return false;
|
|
7341
|
+
}
|
|
7342
|
+
}
|
|
7343
|
+
async function resolveDefaultDebugpyPythonPath(env = process.env) {
|
|
7344
|
+
const legacyVenvPython = getDapCliVenvPythonPath(env);
|
|
7345
|
+
if (await pathExists2(legacyVenvPython) && await pythonHasDebugpy(legacyVenvPython)) {
|
|
7346
|
+
return legacyVenvPython;
|
|
6056
7347
|
}
|
|
6057
|
-
if (pythonHasDebugpy("python3")) {
|
|
7348
|
+
if (await pythonHasDebugpy("python3")) {
|
|
6058
7349
|
return "python3";
|
|
6059
7350
|
}
|
|
6060
|
-
|
|
6061
|
-
|
|
7351
|
+
const adaptersDir = getDapCliAdaptersDir(env);
|
|
7352
|
+
const result = await provisionAdapter("debugpy", {
|
|
7353
|
+
env,
|
|
7354
|
+
assumeYes: resolveAssumeYes(void 0, env),
|
|
7355
|
+
adaptersDir
|
|
7356
|
+
});
|
|
7357
|
+
return result.entrypoint;
|
|
7358
|
+
}
|
|
7359
|
+
|
|
7360
|
+
// src/adapters/builtins/delve.ts
|
|
7361
|
+
import { promises as fs16 } from "fs";
|
|
7362
|
+
import { spawnSync } from "child_process";
|
|
7363
|
+
import path20 from "path";
|
|
7364
|
+
async function createDelveDescriptor(delvePath) {
|
|
7365
|
+
const resolvedDelvePath = delvePath ?? await resolveDefaultDelvePath();
|
|
7366
|
+
assertSupportedProvisionedDelveToolchain(resolvedDelvePath);
|
|
7367
|
+
const toolchainEnvironment = createGoToolchainEnvironment();
|
|
7368
|
+
return {
|
|
7369
|
+
id: "delve",
|
|
7370
|
+
label: "Go Debug Adapter (Delve)",
|
|
7371
|
+
transport: {
|
|
7372
|
+
kind: "server",
|
|
7373
|
+
command: resolvedDelvePath,
|
|
7374
|
+
args: ["dap", "--listen=127.0.0.1:${port}"],
|
|
7375
|
+
host: "127.0.0.1",
|
|
7376
|
+
...toolchainEnvironment === void 0 ? {} : { env: toolchainEnvironment }
|
|
7377
|
+
}
|
|
7378
|
+
};
|
|
7379
|
+
}
|
|
7380
|
+
function createGoToolchainEnvironment() {
|
|
7381
|
+
const goToolchain = process.env.GOTOOLCHAIN;
|
|
7382
|
+
return goToolchain === void 0 || goToolchain.length === 0 ? void 0 : { GOTOOLCHAIN: goToolchain };
|
|
7383
|
+
}
|
|
7384
|
+
async function pathExists3(p) {
|
|
7385
|
+
try {
|
|
7386
|
+
await fs16.access(p);
|
|
7387
|
+
return true;
|
|
7388
|
+
} catch {
|
|
7389
|
+
return false;
|
|
7390
|
+
}
|
|
7391
|
+
}
|
|
7392
|
+
async function resolveDefaultDelvePath(env = process.env) {
|
|
7393
|
+
if (delveIsUsable("dlv")) {
|
|
7394
|
+
return "dlv";
|
|
7395
|
+
}
|
|
7396
|
+
const provisionedDelve = getProvisionedDelvePath(env);
|
|
7397
|
+
if (await pathExists3(provisionedDelve) && delveIsUsable(provisionedDelve)) {
|
|
7398
|
+
return provisionedDelve;
|
|
7399
|
+
}
|
|
7400
|
+
const adaptersDir = getDapCliAdaptersDir(env);
|
|
7401
|
+
const result = await provisionAdapter("delve", {
|
|
7402
|
+
env,
|
|
7403
|
+
assumeYes: resolveAssumeYes(void 0, env),
|
|
7404
|
+
adaptersDir
|
|
7405
|
+
});
|
|
7406
|
+
return result.entrypoint;
|
|
7407
|
+
}
|
|
7408
|
+
function getProvisionedDelvePath(env = process.env) {
|
|
7409
|
+
return path20.join(getDapCliAdaptersDir(env), "delve", process.platform === "win32" ? "dlv.exe" : "dlv");
|
|
7410
|
+
}
|
|
7411
|
+
function delveIsUsable(command) {
|
|
7412
|
+
const result = spawnSync(command, ["version"], { encoding: "utf8" });
|
|
7413
|
+
return result.status === 0;
|
|
7414
|
+
}
|
|
7415
|
+
function assertSupportedProvisionedDelveToolchain(delvePath) {
|
|
7416
|
+
const delveVersion = readCommandVersion(delvePath, ["version"], /Version:\s*v?([0-9]+\.[0-9]+\.[0-9]+)/i);
|
|
7417
|
+
if (delveVersion !== "1.26.3") {
|
|
7418
|
+
return;
|
|
7419
|
+
}
|
|
7420
|
+
const goVersion = readCommandVersion("go", ["version"], /go version go([0-9]+\.[0-9]+(?:\.[0-9]+)?)/i);
|
|
7421
|
+
if (goVersion === void 0 || compareNumericVersions(goVersion, "1.24.0") >= 0) {
|
|
7422
|
+
return;
|
|
7423
|
+
}
|
|
7424
|
+
throw usageError("Provisioned Delve is incompatible with the active Go toolchain.", {
|
|
7425
|
+
code: "delve_go_version_incompatible",
|
|
6062
7426
|
diagnostics: [
|
|
6063
|
-
|
|
6064
|
-
`
|
|
7427
|
+
`Delve ${delveVersion} requires Go 1.24+ for debuggee builds; current \`go\` is ${goVersion}.`,
|
|
7428
|
+
"Use `GOTOOLCHAIN=go1.24.0` for the dap-cli launch, or update the active Go installation.",
|
|
7429
|
+
"Run `go version` in the same shell/environment used for dap-cli to confirm the effective toolchain."
|
|
6065
7430
|
]
|
|
6066
7431
|
});
|
|
6067
7432
|
}
|
|
6068
|
-
function
|
|
6069
|
-
const result = spawnSync(
|
|
6070
|
-
|
|
7433
|
+
function readCommandVersion(command, args, pattern) {
|
|
7434
|
+
const result = spawnSync(command, [...args], { encoding: "utf8" });
|
|
7435
|
+
if (result.status !== 0) {
|
|
7436
|
+
return void 0;
|
|
7437
|
+
}
|
|
7438
|
+
const output = `${result.stdout ?? ""}
|
|
7439
|
+
${result.stderr ?? ""}`;
|
|
7440
|
+
return pattern.exec(output)?.[1];
|
|
7441
|
+
}
|
|
7442
|
+
function compareNumericVersions(left, right) {
|
|
7443
|
+
const leftParts = left.split(".").map((part) => Number.parseInt(part, 10));
|
|
7444
|
+
const rightParts = right.split(".").map((part) => Number.parseInt(part, 10));
|
|
7445
|
+
const width = Math.max(leftParts.length, rightParts.length);
|
|
7446
|
+
for (let index = 0; index < width; index += 1) {
|
|
7447
|
+
const leftPart = leftParts[index] ?? 0;
|
|
7448
|
+
const rightPart = rightParts[index] ?? 0;
|
|
7449
|
+
if (leftPart !== rightPart) {
|
|
7450
|
+
return leftPart - rightPart;
|
|
7451
|
+
}
|
|
7452
|
+
}
|
|
7453
|
+
return 0;
|
|
7454
|
+
}
|
|
7455
|
+
|
|
7456
|
+
// src/adapters/builtins/codelldb.ts
|
|
7457
|
+
import path21 from "path";
|
|
7458
|
+
async function createCodeLldbDescriptor(codelldbPath) {
|
|
7459
|
+
const entrypoint = codelldbPath ?? await resolveDefaultCodeLldbPath();
|
|
7460
|
+
const libLldbPath = path21.join(path21.dirname(entrypoint), "..", "lldb", "lib", "liblldb.dylib");
|
|
7461
|
+
return {
|
|
7462
|
+
id: "codelldb",
|
|
7463
|
+
label: "Rust Debug Adapter (CodeLLDB)",
|
|
7464
|
+
transport: {
|
|
7465
|
+
kind: "server",
|
|
7466
|
+
command: entrypoint,
|
|
7467
|
+
args: ["--liblldb", libLldbPath, "--port", "${port}"],
|
|
7468
|
+
host: "127.0.0.1"
|
|
7469
|
+
}
|
|
7470
|
+
};
|
|
7471
|
+
}
|
|
7472
|
+
async function resolveDefaultCodeLldbPath(env = process.env) {
|
|
7473
|
+
const result = await provisionAdapter("codelldb", {
|
|
7474
|
+
env,
|
|
7475
|
+
assumeYes: resolveAssumeYes(void 0, env),
|
|
7476
|
+
adaptersDir: getDapCliAdaptersDir(env)
|
|
7477
|
+
});
|
|
7478
|
+
return result.entrypoint;
|
|
6071
7479
|
}
|
|
6072
7480
|
|
|
6073
7481
|
// src/adapters/registry.ts
|
|
@@ -6086,6 +7494,16 @@ var AdapterRegistry = class {
|
|
|
6086
7494
|
label: "Python Debug Adapter (debugpy)",
|
|
6087
7495
|
create: () => createDebugpyDescriptor()
|
|
6088
7496
|
});
|
|
7497
|
+
this.builtInAdapters.set("delve", {
|
|
7498
|
+
id: "delve",
|
|
7499
|
+
label: "Go Debug Adapter (Delve)",
|
|
7500
|
+
create: () => createDelveDescriptor()
|
|
7501
|
+
});
|
|
7502
|
+
this.builtInAdapters.set("codelldb", {
|
|
7503
|
+
id: "codelldb",
|
|
7504
|
+
label: "Rust Debug Adapter (CodeLLDB)",
|
|
7505
|
+
create: () => createCodeLldbDescriptor()
|
|
7506
|
+
});
|
|
6089
7507
|
}
|
|
6090
7508
|
for (const descriptor of options.builtInAdapters ?? []) {
|
|
6091
7509
|
this.builtInAdapters.set(descriptor.id, {
|
|
@@ -6098,7 +7516,7 @@ var AdapterRegistry = class {
|
|
|
6098
7516
|
this.customAdapters.set(descriptor.id, descriptor);
|
|
6099
7517
|
}
|
|
6100
7518
|
}
|
|
6101
|
-
resolve(id) {
|
|
7519
|
+
async resolve(id) {
|
|
6102
7520
|
const builtInAdapter = this.builtInAdapters.get(id);
|
|
6103
7521
|
if (builtInAdapter !== void 0) {
|
|
6104
7522
|
return builtInAdapter.create();
|
|
@@ -6127,9 +7545,9 @@ var AdapterRegistry = class {
|
|
|
6127
7545
|
};
|
|
6128
7546
|
|
|
6129
7547
|
// src/config/launchConfig.ts
|
|
6130
|
-
import { promises as
|
|
7548
|
+
import { promises as fs17 } from "fs";
|
|
6131
7549
|
import os from "os";
|
|
6132
|
-
import
|
|
7550
|
+
import path22 from "path";
|
|
6133
7551
|
import { parse } from "jsonc-parser";
|
|
6134
7552
|
import { z as z7 } from "zod";
|
|
6135
7553
|
var launchConfigTypeMap = {
|
|
@@ -6138,7 +7556,9 @@ var launchConfigTypeMap = {
|
|
|
6138
7556
|
chrome: "js-debug",
|
|
6139
7557
|
"pwa-chrome": "js-debug",
|
|
6140
7558
|
python: "debugpy",
|
|
6141
|
-
debugpy: "debugpy"
|
|
7559
|
+
debugpy: "debugpy",
|
|
7560
|
+
go: "delve",
|
|
7561
|
+
lldb: "codelldb"
|
|
6142
7562
|
};
|
|
6143
7563
|
var maxLaunchJsonBytes = 256 * 1024;
|
|
6144
7564
|
var platformKeys = /* @__PURE__ */ new Set(["osx", "mac", "linux", "windows"]);
|
|
@@ -6165,12 +7585,12 @@ function resolveLaunchConfig(sources) {
|
|
|
6165
7585
|
};
|
|
6166
7586
|
}
|
|
6167
7587
|
function resolveLaunchConfigurationConfig(config, options) {
|
|
6168
|
-
const workspaceFolder =
|
|
7588
|
+
const workspaceFolder = path22.resolve(options.workspaceFolder);
|
|
6169
7589
|
const platform = options.platform ?? process.platform;
|
|
6170
7590
|
const merged = applyPlatformOverlay(config, platform);
|
|
6171
7591
|
const context = {
|
|
6172
7592
|
workspaceFolder,
|
|
6173
|
-
workspaceFolderBasename:
|
|
7593
|
+
workspaceFolderBasename: path22.basename(workspaceFolder),
|
|
6174
7594
|
userHome: options.userHome ?? os.homedir(),
|
|
6175
7595
|
execPath: options.execPath ?? process.execPath,
|
|
6176
7596
|
env: options.env ?? process.env
|
|
@@ -6185,20 +7605,20 @@ function resolveLaunchConfigurationConfig(config, options) {
|
|
|
6185
7605
|
for (const key of vscodeOnlyLaunchConfigKeys) {
|
|
6186
7606
|
delete resolved[key];
|
|
6187
7607
|
}
|
|
6188
|
-
return resolved;
|
|
7608
|
+
return normalizeGoLaunchConfigProgram(resolved, workspaceFolder);
|
|
6189
7609
|
}
|
|
6190
7610
|
async function loadVSCodeLaunchJson(cwd) {
|
|
6191
|
-
const workspaceFolder =
|
|
6192
|
-
const launchJsonPath =
|
|
7611
|
+
const workspaceFolder = path22.resolve(cwd);
|
|
7612
|
+
const launchJsonPath = path22.join(cwd, ".vscode", "launch.json");
|
|
6193
7613
|
try {
|
|
6194
|
-
const stat = await
|
|
7614
|
+
const stat = await fs17.stat(launchJsonPath);
|
|
6195
7615
|
if (stat.size > maxLaunchJsonBytes) {
|
|
6196
7616
|
throw usageError("Invalid launch.json.", {
|
|
6197
7617
|
code: "invalid_launch_json",
|
|
6198
7618
|
diagnostics: [".vscode/launch.json is larger than 256KB."]
|
|
6199
7619
|
});
|
|
6200
7620
|
}
|
|
6201
|
-
const raw = await
|
|
7621
|
+
const raw = await fs17.readFile(launchJsonPath, "utf8");
|
|
6202
7622
|
const normalized = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
6203
7623
|
const parsed = launchJsonSchema.parse(parseJsonc(normalized));
|
|
6204
7624
|
return {
|
|
@@ -6303,7 +7723,7 @@ async function applyJsDebugSourceMapDefaults(config, options) {
|
|
|
6303
7723
|
if (!isJsDebugLaunchType(config.type)) {
|
|
6304
7724
|
return config;
|
|
6305
7725
|
}
|
|
6306
|
-
const hasTsConfig = await
|
|
7726
|
+
const hasTsConfig = await pathExists4(path22.join(options.workspaceFolder, "tsconfig.json"));
|
|
6307
7727
|
if (!hasTsConfig) {
|
|
6308
7728
|
return config;
|
|
6309
7729
|
}
|
|
@@ -6313,9 +7733,9 @@ async function applyJsDebugSourceMapDefaults(config, options) {
|
|
|
6313
7733
|
}
|
|
6314
7734
|
if (mapped.outFiles === void 0) {
|
|
6315
7735
|
mapped.outFiles = [
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
7736
|
+
path22.join(options.workspaceFolder, "dist", "**", "*.js"),
|
|
7737
|
+
path22.join(options.workspaceFolder, "out", "**", "*.js"),
|
|
7738
|
+
path22.join(options.workspaceFolder, "build", "**", "*.js")
|
|
6319
7739
|
];
|
|
6320
7740
|
}
|
|
6321
7741
|
return mapped;
|
|
@@ -6328,6 +7748,29 @@ function mapDebugpyFlags(flags) {
|
|
|
6328
7748
|
}
|
|
6329
7749
|
return mapped;
|
|
6330
7750
|
}
|
|
7751
|
+
function validateCodeLldbNativeConfig(config) {
|
|
7752
|
+
if (Object.prototype.hasOwnProperty.call(config, "cargo")) {
|
|
7753
|
+
throw usageError("CodeLLDB Cargo launch configurations are not supported by dap-cli.", {
|
|
7754
|
+
code: "codelldb_cargo_config_unsupported",
|
|
7755
|
+
diagnostics: [
|
|
7756
|
+
"The `cargo` property is resolved by the VS Code CodeLLDB extension and cannot be forwarded to the standalone adapter.",
|
|
7757
|
+
"Build an explicitly built Rust binary and pass its executable path with `program`."
|
|
7758
|
+
],
|
|
7759
|
+
data: { adapterId: "codelldb", unsupportedField: "cargo", requiredField: "program" }
|
|
7760
|
+
});
|
|
7761
|
+
}
|
|
7762
|
+
return config;
|
|
7763
|
+
}
|
|
7764
|
+
function normalizeGoLaunchConfigProgram(config, workspaceFolder) {
|
|
7765
|
+
if (config.type !== "go" || config.request === "attach" || typeof config.program !== "string" || path22.isAbsolute(config.program)) {
|
|
7766
|
+
return config;
|
|
7767
|
+
}
|
|
7768
|
+
const cwd = typeof config.cwd === "string" ? config.cwd : workspaceFolder;
|
|
7769
|
+
return {
|
|
7770
|
+
...config,
|
|
7771
|
+
program: path22.resolve(cwd, config.program)
|
|
7772
|
+
};
|
|
7773
|
+
}
|
|
6331
7774
|
function applyPlatformOverlay(config, platform) {
|
|
6332
7775
|
const base = {};
|
|
6333
7776
|
for (const [key, value] of Object.entries(config)) {
|
|
@@ -6410,9 +7853,9 @@ function resolveLaunchString(value, jsonPath, context) {
|
|
|
6410
7853
|
});
|
|
6411
7854
|
});
|
|
6412
7855
|
}
|
|
6413
|
-
async function
|
|
7856
|
+
async function pathExists4(filePath) {
|
|
6414
7857
|
try {
|
|
6415
|
-
await
|
|
7858
|
+
await fs17.stat(filePath);
|
|
6416
7859
|
return true;
|
|
6417
7860
|
} catch (error) {
|
|
6418
7861
|
if (isNodeError5(error) && error.code === "ENOENT") {
|
|
@@ -6454,9 +7897,10 @@ function isNodeError5(error) {
|
|
|
6454
7897
|
}
|
|
6455
7898
|
|
|
6456
7899
|
// src/config/programInference.ts
|
|
6457
|
-
import
|
|
7900
|
+
import path23 from "path";
|
|
6458
7901
|
var extensionTable = {
|
|
6459
7902
|
".py": { adapterId: "debugpy", type: "python" },
|
|
7903
|
+
".go": { adapterId: "delve", type: "go" },
|
|
6460
7904
|
".js": { adapterId: "js-debug", type: "pwa-node" },
|
|
6461
7905
|
".mjs": { adapterId: "js-debug", type: "pwa-node" },
|
|
6462
7906
|
".cjs": { adapterId: "js-debug", type: "pwa-node" },
|
|
@@ -6484,7 +7928,7 @@ function inferAdapterAndType(args) {
|
|
|
6484
7928
|
return { adapterId, type, inferred: { adapter: true, type: false } };
|
|
6485
7929
|
}
|
|
6486
7930
|
if (program !== void 0) {
|
|
6487
|
-
const extension =
|
|
7931
|
+
const extension = path23.extname(program).toLowerCase();
|
|
6488
7932
|
const match = extensionTable[extension];
|
|
6489
7933
|
if (match === void 0) {
|
|
6490
7934
|
throw usageError(`Cannot infer adapter from program extension '${extension}'. Pass --adapter or --type explicitly.`, {
|
|
@@ -6500,7 +7944,7 @@ function inferAdapterAndType(args) {
|
|
|
6500
7944
|
function defaultTypeForAdapter(adapterId, program) {
|
|
6501
7945
|
if (adapterId === "js-debug") {
|
|
6502
7946
|
if (program !== void 0) {
|
|
6503
|
-
const ext =
|
|
7947
|
+
const ext = path23.extname(program).toLowerCase();
|
|
6504
7948
|
if (ext === ".html" || ext === ".htm") {
|
|
6505
7949
|
return "pwa-chrome";
|
|
6506
7950
|
}
|
|
@@ -6510,6 +7954,12 @@ function defaultTypeForAdapter(adapterId, program) {
|
|
|
6510
7954
|
if (adapterId === "debugpy") {
|
|
6511
7955
|
return "python";
|
|
6512
7956
|
}
|
|
7957
|
+
if (adapterId === "delve") {
|
|
7958
|
+
return "go";
|
|
7959
|
+
}
|
|
7960
|
+
if (adapterId === "codelldb") {
|
|
7961
|
+
return "lldb";
|
|
7962
|
+
}
|
|
6513
7963
|
return void 0;
|
|
6514
7964
|
}
|
|
6515
7965
|
|
|
@@ -6551,7 +8001,7 @@ function createNameParams(name) {
|
|
|
6551
8001
|
return name === void 0 ? {} : { name };
|
|
6552
8002
|
}
|
|
6553
8003
|
async function startDap(output, mode, options) {
|
|
6554
|
-
const workspace =
|
|
8004
|
+
const workspace = path24.resolve(options.workspace ?? process.cwd());
|
|
6555
8005
|
if (options.listConfigs === true) {
|
|
6556
8006
|
output.success(listLaunchConfigEntries(await loadVSCodeLaunchJson(workspace)), { command: "launch configs" });
|
|
6557
8007
|
return;
|
|
@@ -6595,7 +8045,7 @@ async function startDap(output, mode, options) {
|
|
|
6595
8045
|
...await mapConfigForAdapter(adapterId, resolveLaunchConfig({ namedConfig: { ...adapterDefaults, ...namedConfig }, jsonOverrides, jsonConfig, flags: adapterFlags }), workspace),
|
|
6596
8046
|
request: effectiveMode
|
|
6597
8047
|
};
|
|
6598
|
-
const descriptor = adapterId === "fake" ? createFakeDescriptor(options.script ?? (effectiveMode === "attach" ? "attach-stopped" : "stopped-on-entry"), effectiveMode) : new AdapterRegistry({ config: adapterConfig }).resolve(adapterId);
|
|
8048
|
+
const descriptor = adapterId === "fake" ? createFakeDescriptor(options.script ?? (effectiveMode === "attach" ? "attach-stopped" : "stopped-on-entry"), effectiveMode) : await new AdapterRegistry({ config: adapterConfig }).resolve(adapterId);
|
|
6599
8049
|
const client = await createControllerClient({ dapCliHome: process.env.DAP_CLI_HOME, timeoutMs: startControllerRequestTimeoutMs });
|
|
6600
8050
|
try {
|
|
6601
8051
|
const response = await client.request("dap.start", {
|
|
@@ -6625,7 +8075,7 @@ async function createCompoundStartMember(configuration, memberName, workspaceFol
|
|
|
6625
8075
|
...await mapConfigForAdapter(adapterId, resolveLaunchConfig({ namedConfig: { ...adapterDefaults, ...resolvedConfig }, jsonOverrides, jsonConfig, flags: adapterFlags }), workspaceFolder),
|
|
6626
8076
|
request: memberMode
|
|
6627
8077
|
};
|
|
6628
|
-
const descriptor = adapterId === "fake" ? createFakeDescriptor(options.script ?? (memberMode === "attach" ? "attach-stopped" : "stopped-on-entry"), memberMode) : new AdapterRegistry({ config: adapterConfig }).resolve(adapterId);
|
|
8078
|
+
const descriptor = adapterId === "fake" ? createFakeDescriptor(options.script ?? (memberMode === "attach" ? "attach-stopped" : "stopped-on-entry"), memberMode) : await new AdapterRegistry({ config: adapterConfig }).resolve(adapterId);
|
|
6629
8079
|
return { memberName, mode: memberMode, descriptor, config };
|
|
6630
8080
|
}
|
|
6631
8081
|
function getAdapterDefaults(adapterConfig, adapterId, mode) {
|
|
@@ -6704,6 +8154,12 @@ async function mapConfigForAdapter(adapterId, config, workspaceFolder) {
|
|
|
6704
8154
|
if (adapterId === "debugpy") {
|
|
6705
8155
|
return mapDebugpyFlags(config);
|
|
6706
8156
|
}
|
|
8157
|
+
if (adapterId === "delve") {
|
|
8158
|
+
return normalizeGoLaunchConfigProgram(config, workspaceFolder);
|
|
8159
|
+
}
|
|
8160
|
+
if (adapterId === "codelldb") {
|
|
8161
|
+
return validateCodeLldbNativeConfig(config);
|
|
8162
|
+
}
|
|
6707
8163
|
return config;
|
|
6708
8164
|
}
|
|
6709
8165
|
function setIfDefined(target, key, value) {
|
|
@@ -6727,7 +8183,7 @@ function createFakeDescriptor(script, mode) {
|
|
|
6727
8183
|
transport: {
|
|
6728
8184
|
kind: "stdio",
|
|
6729
8185
|
command: process.execPath,
|
|
6730
|
-
args: ["--experimental-strip-types",
|
|
8186
|
+
args: ["--experimental-strip-types", path24.join(process.cwd(), "tests", "fixtures", "fake-adapter-entry.ts"), "--script", script, "--mode", mode]
|
|
6731
8187
|
}
|
|
6732
8188
|
};
|
|
6733
8189
|
}
|
|
@@ -6843,6 +8299,139 @@ function createNameParams2(name) {
|
|
|
6843
8299
|
return name === void 0 ? {} : { name };
|
|
6844
8300
|
}
|
|
6845
8301
|
|
|
8302
|
+
// src/cli/commands/setupAdapters.ts
|
|
8303
|
+
import path25 from "path";
|
|
8304
|
+
import { promises as fs18 } from "fs";
|
|
8305
|
+
import { Option } from "commander";
|
|
8306
|
+
var ALL_ADAPTERS = ["js-debug", "debugpy", "delve", "codelldb"];
|
|
8307
|
+
var ADAPTER_VERSIONS = {
|
|
8308
|
+
"js-debug": JS_DEBUG_VERSION,
|
|
8309
|
+
"debugpy": DEBUGPY_VERSION,
|
|
8310
|
+
"delve": DELVE_VERSION,
|
|
8311
|
+
"codelldb": CODELLDB_VERSION
|
|
8312
|
+
};
|
|
8313
|
+
function expectedEntrypoint(adaptersDir, id) {
|
|
8314
|
+
switch (id) {
|
|
8315
|
+
case "js-debug":
|
|
8316
|
+
return path25.join(adaptersDir, "js-debug", "src", "dapDebugServer.js");
|
|
8317
|
+
case "debugpy":
|
|
8318
|
+
return path25.join(
|
|
8319
|
+
adaptersDir,
|
|
8320
|
+
"debugpy",
|
|
8321
|
+
process.platform === "win32" ? path25.join("venv", "Scripts", "python.exe") : path25.join("venv", "bin", "python")
|
|
8322
|
+
);
|
|
8323
|
+
case "delve":
|
|
8324
|
+
return path25.join(adaptersDir, "delve", process.platform === "win32" ? "dlv.exe" : "dlv");
|
|
8325
|
+
case "codelldb":
|
|
8326
|
+
return path25.join(adaptersDir, "codelldb", "extension", "adapter", "codelldb");
|
|
8327
|
+
}
|
|
8328
|
+
}
|
|
8329
|
+
async function pathExists5(p) {
|
|
8330
|
+
try {
|
|
8331
|
+
await fs18.access(p);
|
|
8332
|
+
return true;
|
|
8333
|
+
} catch {
|
|
8334
|
+
return false;
|
|
8335
|
+
}
|
|
8336
|
+
}
|
|
8337
|
+
function isAdapterId(value) {
|
|
8338
|
+
return value === "js-debug" || value === "debugpy" || value === "delve" || value === "codelldb";
|
|
8339
|
+
}
|
|
8340
|
+
async function isRuntimeReady(adaptersDir, id) {
|
|
8341
|
+
return id === "codelldb" ? isCodeLldbRuntimeReady(path25.join(adaptersDir, id)) : pathExists5(expectedEntrypoint(adaptersDir, id));
|
|
8342
|
+
}
|
|
8343
|
+
async function runSetupAdaptersAction(opts) {
|
|
8344
|
+
const targets = opts.adapter !== void 0 ? [opts.adapter] : ALL_ADAPTERS;
|
|
8345
|
+
const adaptersDir = getDapCliAdaptersDir(opts.env);
|
|
8346
|
+
await fs18.mkdir(adaptersDir, { recursive: true });
|
|
8347
|
+
const pending = [];
|
|
8348
|
+
for (const id of targets) {
|
|
8349
|
+
const version = ADAPTER_VERSIONS[id];
|
|
8350
|
+
const cached = await hasConsentMarker(adaptersDir, id, version) && await isRuntimeReady(adaptersDir, id);
|
|
8351
|
+
if (!cached) {
|
|
8352
|
+
pending.push(id);
|
|
8353
|
+
}
|
|
8354
|
+
}
|
|
8355
|
+
if (pending.length > 0 && !opts.assumeYes) {
|
|
8356
|
+
const pendingDescription = pending.map((id) => `${id} ${ADAPTER_VERSIONS[id]}`).join(", ");
|
|
8357
|
+
await confirm({
|
|
8358
|
+
assumeYes: false,
|
|
8359
|
+
question: `Install ${pending.length} adapter${pending.length === 1 ? "" : "s"} (${pendingDescription}) into ${adaptersDir}/?`,
|
|
8360
|
+
...opts.stdin === void 0 ? {} : { stdin: opts.stdin },
|
|
8361
|
+
...opts.stderr === void 0 ? {} : { stderr: opts.stderr }
|
|
8362
|
+
});
|
|
8363
|
+
}
|
|
8364
|
+
const innerCtxBase = {
|
|
8365
|
+
env: opts.env,
|
|
8366
|
+
adaptersDir,
|
|
8367
|
+
...opts.stdin === void 0 ? {} : { stdin: opts.stdin },
|
|
8368
|
+
...opts.stderr === void 0 ? {} : { stderr: opts.stderr }
|
|
8369
|
+
};
|
|
8370
|
+
const entries = [];
|
|
8371
|
+
for (const id of targets) {
|
|
8372
|
+
const version = ADAPTER_VERSIONS[id];
|
|
8373
|
+
try {
|
|
8374
|
+
const result = await provisionAdapter(id, { ...innerCtxBase, assumeYes: true });
|
|
8375
|
+
const entry = {
|
|
8376
|
+
id,
|
|
8377
|
+
version: result.version,
|
|
8378
|
+
status: result.fromCache ? "cached" : "installed",
|
|
8379
|
+
installRoot: result.installRoot
|
|
8380
|
+
};
|
|
8381
|
+
entries.push(entry);
|
|
8382
|
+
if (opts.output !== void 0) {
|
|
8383
|
+
const verb = result.fromCache ? "already cached" : "installed";
|
|
8384
|
+
opts.output.warn(`${id} ${result.version}: ${verb} (${result.installRoot})`);
|
|
8385
|
+
}
|
|
8386
|
+
} catch (err) {
|
|
8387
|
+
const cliError = err instanceof CliError ? err : null;
|
|
8388
|
+
const code = cliError?.code ?? "internal_error";
|
|
8389
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8390
|
+
entries.push({
|
|
8391
|
+
id,
|
|
8392
|
+
version,
|
|
8393
|
+
status: "failed",
|
|
8394
|
+
error: {
|
|
8395
|
+
code,
|
|
8396
|
+
message,
|
|
8397
|
+
diagnostics: cliError?.diagnostics ?? [message]
|
|
8398
|
+
}
|
|
8399
|
+
});
|
|
8400
|
+
if (opts.output !== void 0) {
|
|
8401
|
+
opts.output.warn(`${id} ${version}: FAILED (${code})`);
|
|
8402
|
+
}
|
|
8403
|
+
}
|
|
8404
|
+
}
|
|
8405
|
+
return { adapters: entries };
|
|
8406
|
+
}
|
|
8407
|
+
function registerSetupAdaptersCommand(program, output) {
|
|
8408
|
+
program.command("setup-adapters").helpGroup("Adapters").description("Install or update built-in debug adapters (js-debug, debugpy, delve, codelldb) into ~/.dap-cli/adapters/").addOption(
|
|
8409
|
+
new Option("--adapter <id>", "install only the named adapter").choices(["js-debug", "debugpy", "delve", "codelldb"])
|
|
8410
|
+
).action(async (cmdOpts) => {
|
|
8411
|
+
const adapterId = cmdOpts.adapter !== void 0 && isAdapterId(cmdOpts.adapter) ? cmdOpts.adapter : void 0;
|
|
8412
|
+
const assumeYes = resolveAssumeYes(void 0, process.env);
|
|
8413
|
+
const result = await runSetupAdaptersAction({
|
|
8414
|
+
...adapterId === void 0 ? {} : { adapter: adapterId },
|
|
8415
|
+
assumeYes,
|
|
8416
|
+
env: process.env,
|
|
8417
|
+
stdin: process.stdin,
|
|
8418
|
+
stderr: process.stderr,
|
|
8419
|
+
stdout: process.stdout,
|
|
8420
|
+
output
|
|
8421
|
+
});
|
|
8422
|
+
const failed = result.adapters.filter((a) => a.status === "failed");
|
|
8423
|
+
if (failed.length > 0) {
|
|
8424
|
+
const failedNames = failed.map((a) => `${a.id} (${a.error?.code ?? "internal_error"})`).join(", ");
|
|
8425
|
+
throw usageError(`Adapter setup failed for: ${failedNames}`, {
|
|
8426
|
+
code: "provision_setup_failed",
|
|
8427
|
+
diagnostics: failed.flatMap((a) => a.error?.diagnostics ?? []),
|
|
8428
|
+
data: { adapters: result.adapters }
|
|
8429
|
+
});
|
|
8430
|
+
}
|
|
8431
|
+
output.success(result, { command: "setup-adapters" });
|
|
8432
|
+
});
|
|
8433
|
+
}
|
|
8434
|
+
|
|
6846
8435
|
// src/cli/program.ts
|
|
6847
8436
|
var require2 = createRequire(import.meta.url);
|
|
6848
8437
|
function loadPackageJson() {
|
|
@@ -6867,12 +8456,19 @@ function createProgram(options = {}) {
|
|
|
6867
8456
|
resolveMode: () => resolveOutputMode({ cliHuman: getProgramHumanOption(program), isStdoutTTY: stdout.isTTY === true, env: process.env })
|
|
6868
8457
|
});
|
|
6869
8458
|
program[PROGRAM_OUTPUT_WRITER] = output;
|
|
6870
|
-
program.name("dap-cli").description("A Debug Adapter Protocol CLI for agents. Control debug sessions from shell commands.").version(getPackageVersion(packageJson)).option("--human", "render human-readable output (default when stdout is a TTY and DAP_CLI_HUMAN is set)").option("--no-human", "render machine-readable JSON output even if DAP_CLI_HUMAN is set or stdout is a TTY").showHelpAfterError().exitOverride();
|
|
8459
|
+
program.name("dap-cli").description("A Debug Adapter Protocol CLI for agents. Control debug sessions from shell commands.").version(getPackageVersion(packageJson)).option("--human", "render human-readable output (default when stdout is a TTY and DAP_CLI_HUMAN is set)").option("--no-human", "render machine-readable JSON output even if DAP_CLI_HUMAN is set or stdout is a TTY").option("-y, --yes", "pre-consent to adapter provisioning prompts (equivalent to DAP_CLI_ASSUME_YES=1)").showHelpAfterError().exitOverride();
|
|
8460
|
+
program.hook("preAction", (thisCommand) => {
|
|
8461
|
+
const opts = thisCommand.opts();
|
|
8462
|
+
if (opts.yes === true) {
|
|
8463
|
+
process.env.DAP_CLI_ASSUME_YES = "1";
|
|
8464
|
+
}
|
|
8465
|
+
});
|
|
6871
8466
|
registerControllerCommands(program, output);
|
|
6872
8467
|
registerSessionCommands(program, output);
|
|
6873
8468
|
registerDapCoreCommands(program, output);
|
|
6874
8469
|
registerGeneratedDapCommands(program, output);
|
|
6875
8470
|
registerDapAliasCommands(program, output);
|
|
8471
|
+
registerSetupAdaptersCommand(program, output);
|
|
6876
8472
|
program.helpCommand(false);
|
|
6877
8473
|
program.command("help [command...]").description("Display help for a command. Pass multiple words to drill into subcommands (e.g. `dap-cli help breakpoints set`).").action((commandPath) => {
|
|
6878
8474
|
const segments = Array.isArray(commandPath) ? commandPath : [];
|