@serviceme/devtools-core 0.4.5 → 0.4.7

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/dist/index.mjs CHANGED
@@ -1325,7 +1325,8 @@ import {
1325
1325
 
1326
1326
  // src/process/runCommand.ts
1327
1327
  import { spawn } from "child_process";
1328
- function terminateCommandProcess(child) {
1328
+ var FORCE_KILL_DELAY_MS = 2e3;
1329
+ function terminateCommandProcess(child, signal = "SIGTERM") {
1329
1330
  if (child.killed) {
1330
1331
  return;
1331
1332
  }
@@ -1339,7 +1340,32 @@ function terminateCommandProcess(child) {
1339
1340
  });
1340
1341
  return;
1341
1342
  }
1342
- child.kill("SIGTERM");
1343
+ const pid = child.pid;
1344
+ if (pid) {
1345
+ try {
1346
+ process.kill(-pid, signal);
1347
+ return;
1348
+ } catch {
1349
+ }
1350
+ }
1351
+ child.kill(signal);
1352
+ }
1353
+ function scheduleForceKill(child) {
1354
+ if (process.platform === "win32") {
1355
+ return void 0;
1356
+ }
1357
+ const pid = child.pid;
1358
+ if (pid === void 0) {
1359
+ return void 0;
1360
+ }
1361
+ const timer = setTimeout(() => {
1362
+ try {
1363
+ process.kill(-pid, "SIGKILL");
1364
+ } catch {
1365
+ }
1366
+ }, FORCE_KILL_DELAY_MS);
1367
+ timer.unref?.();
1368
+ return timer;
1343
1369
  }
1344
1370
  async function runCommand(command, options = {}) {
1345
1371
  return new Promise((resolve3, reject) => {
@@ -1348,12 +1374,16 @@ async function runCommand(command, options = {}) {
1348
1374
  env: options.env,
1349
1375
  shell: options.shell,
1350
1376
  stdio: "pipe",
1351
- windowsHide: true
1377
+ windowsHide: true,
1378
+ // POSIX: run the command in its own process group so a timeout can
1379
+ // terminate the whole tree (login shell + its children) at once.
1380
+ detached: process.platform !== "win32"
1352
1381
  });
1353
1382
  let stdout = "";
1354
1383
  let stderr = "";
1355
1384
  let finished = false;
1356
1385
  let timeoutId;
1386
+ let forceKillTimer;
1357
1387
  const finish = (handler) => {
1358
1388
  if (finished) {
1359
1389
  return;
@@ -1362,6 +1392,9 @@ async function runCommand(command, options = {}) {
1362
1392
  if (timeoutId) {
1363
1393
  clearTimeout(timeoutId);
1364
1394
  }
1395
+ if (forceKillTimer) {
1396
+ clearTimeout(forceKillTimer);
1397
+ }
1365
1398
  handler();
1366
1399
  };
1367
1400
  child.stdout?.setEncoding("utf8");
@@ -1376,6 +1409,10 @@ async function runCommand(command, options = {}) {
1376
1409
  finish(() => reject(error));
1377
1410
  });
1378
1411
  child.once("close", (code) => {
1412
+ if (forceKillTimer) {
1413
+ clearTimeout(forceKillTimer);
1414
+ forceKillTimer = void 0;
1415
+ }
1379
1416
  finish(() => {
1380
1417
  if (code === 0) {
1381
1418
  resolve3({
@@ -1411,6 +1448,7 @@ async function runCommand(command, options = {}) {
1411
1448
  error.stderr = stderr;
1412
1449
  reject(error);
1413
1450
  });
1451
+ forceKillTimer = scheduleForceKill(child);
1414
1452
  }, options.timeoutMs);
1415
1453
  }
1416
1454
  });
@@ -2525,6 +2563,9 @@ async function collectRecursive(absDir, root, out) {
2525
2563
  }
2526
2564
 
2527
2565
  // src/env/environmentInspector.ts
2566
+ import { existsSync } from "fs";
2567
+ import { homedir as homedir2 } from "os";
2568
+ import { join as join7 } from "path";
2528
2569
  import {
2529
2570
  createServicemeError as createServicemeError4,
2530
2571
  KNOWN_ENVIRONMENT_TOOLS
@@ -2575,11 +2616,19 @@ function posixLoginShellArgs(platform3, envShell, command) {
2575
2616
  }
2576
2617
  var EnvironmentInspector = class {
2577
2618
  constructor(options = {}) {
2619
+ /**
2620
+ * Per-check-cycle result cache. `checkEnvironment()` clears it before
2621
+ * probing so a fresh cycle never returns stale results, while concurrent
2622
+ * or repeated `checkTool()` calls within the same cycle share one probe
2623
+ * (deduplicating expensive login-shell fallbacks).
2624
+ */
2625
+ this.toolCheckCache = /* @__PURE__ */ new Map();
2578
2626
  this.runCommandFn = options.runCommand ?? runCommand;
2579
2627
  this.platform = options.platform ?? process.platform;
2580
2628
  this.shell = options.shell ?? process.env.SHELL;
2581
2629
  }
2582
2630
  async checkEnvironment() {
2631
+ this.toolCheckCache.clear();
2583
2632
  const results = await Promise.all(
2584
2633
  KNOWN_ENVIRONMENT_TOOLS.map(async (tool) => [tool, await this.checkTool(tool)])
2585
2634
  );
@@ -2589,6 +2638,15 @@ var EnvironmentInspector = class {
2589
2638
  if (!/^[a-zA-Z0-9-]+$/.test(toolName)) {
2590
2639
  throw createServicemeError4("invalid_params", "Invalid tool name.");
2591
2640
  }
2641
+ const cached = this.toolCheckCache.get(toolName);
2642
+ if (cached) {
2643
+ return cached;
2644
+ }
2645
+ const check = this.performToolCheck(toolName);
2646
+ this.toolCheckCache.set(toolName, check);
2647
+ return check;
2648
+ }
2649
+ async performToolCheck(toolName) {
2592
2650
  try {
2593
2651
  if (toolName === "nvm") {
2594
2652
  return await this.checkNvm();
@@ -2700,6 +2758,12 @@ var EnvironmentInspector = class {
2700
2758
  }
2701
2759
  }
2702
2760
  try {
2761
+ if (!this.resolveNvmShPath()) {
2762
+ return {
2763
+ installed: false,
2764
+ error: "Not installed"
2765
+ };
2766
+ }
2703
2767
  const nvmArgs = posixLoginShellArgs(this.platform, this.shell, "nvm --version");
2704
2768
  const result = await this.runCommandFn(nvmArgs.command, {
2705
2769
  args: nvmArgs.args,
@@ -2717,6 +2781,28 @@ var EnvironmentInspector = class {
2717
2781
  };
2718
2782
  }
2719
2783
  }
2784
+ /**
2785
+ * Resolve `nvm.sh` from `$NVM_DIR` and the known install roots without
2786
+ * spawning a shell. Returns undefined when nvm is not installed in any
2787
+ * well-known location, letting callers skip a login-shell probe.
2788
+ */
2789
+ resolveNvmShPath() {
2790
+ const candidates = [];
2791
+ const nvmDir = process.env.NVM_DIR?.trim();
2792
+ if (nvmDir && nvmDir.length > 0) {
2793
+ candidates.push(nvmDir);
2794
+ }
2795
+ for (const dir of NVM_FALLBACK_DIRS) {
2796
+ candidates.push(dir.replace(/^\$HOME/, homedir2()));
2797
+ }
2798
+ for (const candidate of candidates) {
2799
+ const scriptPath = join7(candidate, "nvm.sh");
2800
+ if (existsSync(scriptPath)) {
2801
+ return scriptPath;
2802
+ }
2803
+ }
2804
+ return void 0;
2805
+ }
2720
2806
  async checkNuget() {
2721
2807
  const dotnetPath = await this.getToolPath("dotnet");
2722
2808
  if (!dotnetPath) {
@@ -3433,7 +3519,7 @@ import {
3433
3519
  // src/utils/fileUtils.ts
3434
3520
  import { constants, createWriteStream } from "fs";
3435
3521
  import { access as access5, copyFile, lstat, mkdir as mkdir5, readdir as readdir3, rename as rename4, rm as rm3 } from "fs/promises";
3436
- import { dirname as dirname7, join as join8 } from "path";
3522
+ import { dirname as dirname7, join as join9 } from "path";
3437
3523
  import yauzl from "yauzl";
3438
3524
  var unzipFile = (zipPath, dest) => {
3439
3525
  return new Promise((resolve3, reject) => {
@@ -3443,11 +3529,11 @@ var unzipFile = (zipPath, dest) => {
3443
3529
  zipfile.readEntry();
3444
3530
  zipfile.on("entry", (entry) => {
3445
3531
  if (/\/$/.test(entry.fileName)) {
3446
- void mkdir5(join8(dest, entry.fileName), { recursive: true }).then(() => {
3532
+ void mkdir5(join9(dest, entry.fileName), { recursive: true }).then(() => {
3447
3533
  zipfile.readEntry();
3448
3534
  }).catch(reject);
3449
3535
  } else {
3450
- const outputPath = join8(dest, entry.fileName);
3536
+ const outputPath = join9(dest, entry.fileName);
3451
3537
  void mkdir5(dirname7(outputPath), { recursive: true }).then(() => {
3452
3538
  zipfile.openReadStream(
3453
3539
  entry,
@@ -3496,7 +3582,7 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
3496
3582
  await mkdir5(destPath, { recursive: true });
3497
3583
  const children = await readdir3(sourcePath);
3498
3584
  for (const child of children) {
3499
- await mergeEntry(join8(sourcePath, child), join8(destPath, child), overwrite);
3585
+ await mergeEntry(join9(sourcePath, child), join9(destPath, child), overwrite);
3500
3586
  }
3501
3587
  await rm3(sourcePath, { recursive: true, force: true });
3502
3588
  return;
@@ -3519,8 +3605,8 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
3519
3605
  await mkdir5(destDir, { recursive: true });
3520
3606
  const files = await readdir3(sourceDir);
3521
3607
  for (const file of files) {
3522
- const sourceFile = join8(sourceDir, file);
3523
- const destFile = join8(destDir, file);
3608
+ const sourceFile = join9(sourceDir, file);
3609
+ const destFile = join9(destDir, file);
3524
3610
  if (!overwrite) {
3525
3611
  try {
3526
3612
  await access5(destFile, constants.F_OK);