@serviceme/devtools-core 0.4.5 → 0.4.6

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.d.mts CHANGED
@@ -266,9 +266,17 @@ declare class EnvironmentInspector {
266
266
  private readonly runCommandFn;
267
267
  private readonly platform;
268
268
  private readonly shell;
269
+ /**
270
+ * Per-check-cycle result cache. `checkEnvironment()` clears it before
271
+ * probing so a fresh cycle never returns stale results, while concurrent
272
+ * or repeated `checkTool()` calls within the same cycle share one probe
273
+ * (deduplicating expensive login-shell fallbacks).
274
+ */
275
+ private readonly toolCheckCache;
269
276
  constructor(options?: EnvironmentInspectorOptions);
270
277
  checkEnvironment(): Promise<EnvironmentCheckResult>;
271
278
  checkTool(toolName: KnownEnvironmentTool): Promise<ToolCheckResult>;
279
+ private performToolCheck;
272
280
  private getToolPath;
273
281
  private getToolPathFromLoginShell;
274
282
  /**
@@ -281,6 +289,12 @@ declare class EnvironmentInspector {
281
289
  private getToolVersion;
282
290
  private shouldUsePosixLoginShellFallback;
283
291
  private checkNvm;
292
+ /**
293
+ * Resolve `nvm.sh` from `$NVM_DIR` and the known install roots without
294
+ * spawning a shell. Returns undefined when nvm is not installed in any
295
+ * well-known location, letting callers skip a login-shell probe.
296
+ */
297
+ private resolveNvmShPath;
284
298
  private checkNuget;
285
299
  private getVersionInvocation;
286
300
  private getToolTimeout;
package/dist/index.d.ts CHANGED
@@ -266,9 +266,17 @@ declare class EnvironmentInspector {
266
266
  private readonly runCommandFn;
267
267
  private readonly platform;
268
268
  private readonly shell;
269
+ /**
270
+ * Per-check-cycle result cache. `checkEnvironment()` clears it before
271
+ * probing so a fresh cycle never returns stale results, while concurrent
272
+ * or repeated `checkTool()` calls within the same cycle share one probe
273
+ * (deduplicating expensive login-shell fallbacks).
274
+ */
275
+ private readonly toolCheckCache;
269
276
  constructor(options?: EnvironmentInspectorOptions);
270
277
  checkEnvironment(): Promise<EnvironmentCheckResult>;
271
278
  checkTool(toolName: KnownEnvironmentTool): Promise<ToolCheckResult>;
279
+ private performToolCheck;
272
280
  private getToolPath;
273
281
  private getToolPathFromLoginShell;
274
282
  /**
@@ -281,6 +289,12 @@ declare class EnvironmentInspector {
281
289
  private getToolVersion;
282
290
  private shouldUsePosixLoginShellFallback;
283
291
  private checkNvm;
292
+ /**
293
+ * Resolve `nvm.sh` from `$NVM_DIR` and the known install roots without
294
+ * spawning a shell. Returns undefined when nvm is not installed in any
295
+ * well-known location, letting callers skip a login-shell probe.
296
+ */
297
+ private resolveNvmShPath;
284
298
  private checkNuget;
285
299
  private getVersionInvocation;
286
300
  private getToolTimeout;
package/dist/index.js CHANGED
@@ -1519,7 +1519,8 @@ var import_devtools_protocol2 = require("@serviceme/devtools-protocol");
1519
1519
 
1520
1520
  // src/process/runCommand.ts
1521
1521
  var import_node_child_process = require("child_process");
1522
- function terminateCommandProcess(child) {
1522
+ var FORCE_KILL_DELAY_MS = 2e3;
1523
+ function terminateCommandProcess(child, signal = "SIGTERM") {
1523
1524
  if (child.killed) {
1524
1525
  return;
1525
1526
  }
@@ -1533,7 +1534,32 @@ function terminateCommandProcess(child) {
1533
1534
  });
1534
1535
  return;
1535
1536
  }
1536
- child.kill("SIGTERM");
1537
+ const pid = child.pid;
1538
+ if (pid) {
1539
+ try {
1540
+ process.kill(-pid, signal);
1541
+ return;
1542
+ } catch {
1543
+ }
1544
+ }
1545
+ child.kill(signal);
1546
+ }
1547
+ function scheduleForceKill(child) {
1548
+ if (process.platform === "win32") {
1549
+ return void 0;
1550
+ }
1551
+ const pid = child.pid;
1552
+ if (pid === void 0) {
1553
+ return void 0;
1554
+ }
1555
+ const timer = setTimeout(() => {
1556
+ try {
1557
+ process.kill(-pid, "SIGKILL");
1558
+ } catch {
1559
+ }
1560
+ }, FORCE_KILL_DELAY_MS);
1561
+ timer.unref?.();
1562
+ return timer;
1537
1563
  }
1538
1564
  async function runCommand(command, options = {}) {
1539
1565
  return new Promise((resolve3, reject) => {
@@ -1542,12 +1568,16 @@ async function runCommand(command, options = {}) {
1542
1568
  env: options.env,
1543
1569
  shell: options.shell,
1544
1570
  stdio: "pipe",
1545
- windowsHide: true
1571
+ windowsHide: true,
1572
+ // POSIX: run the command in its own process group so a timeout can
1573
+ // terminate the whole tree (login shell + its children) at once.
1574
+ detached: process.platform !== "win32"
1546
1575
  });
1547
1576
  let stdout = "";
1548
1577
  let stderr = "";
1549
1578
  let finished = false;
1550
1579
  let timeoutId;
1580
+ let forceKillTimer;
1551
1581
  const finish = (handler) => {
1552
1582
  if (finished) {
1553
1583
  return;
@@ -1556,6 +1586,9 @@ async function runCommand(command, options = {}) {
1556
1586
  if (timeoutId) {
1557
1587
  clearTimeout(timeoutId);
1558
1588
  }
1589
+ if (forceKillTimer) {
1590
+ clearTimeout(forceKillTimer);
1591
+ }
1559
1592
  handler();
1560
1593
  };
1561
1594
  child.stdout?.setEncoding("utf8");
@@ -1570,6 +1603,10 @@ async function runCommand(command, options = {}) {
1570
1603
  finish(() => reject(error));
1571
1604
  });
1572
1605
  child.once("close", (code) => {
1606
+ if (forceKillTimer) {
1607
+ clearTimeout(forceKillTimer);
1608
+ forceKillTimer = void 0;
1609
+ }
1573
1610
  finish(() => {
1574
1611
  if (code === 0) {
1575
1612
  resolve3({
@@ -1605,6 +1642,7 @@ async function runCommand(command, options = {}) {
1605
1642
  error.stderr = stderr;
1606
1643
  reject(error);
1607
1644
  });
1645
+ forceKillTimer = scheduleForceKill(child);
1608
1646
  }, options.timeoutMs);
1609
1647
  }
1610
1648
  });
@@ -2715,6 +2753,9 @@ async function collectRecursive(absDir, root, out) {
2715
2753
  }
2716
2754
 
2717
2755
  // src/env/environmentInspector.ts
2756
+ var import_node_fs = require("fs");
2757
+ var import_node_os = require("os");
2758
+ var import_node_path = require("path");
2718
2759
  var import_devtools_protocol4 = require("@serviceme/devtools-protocol");
2719
2760
  var DEFAULT_TOOL_CHECK_TIMEOUT_MS = 5e3;
2720
2761
  var TOOL_CHECK_TIMEOUT_MS = {
@@ -2762,11 +2803,19 @@ function posixLoginShellArgs(platform3, envShell, command) {
2762
2803
  }
2763
2804
  var EnvironmentInspector = class {
2764
2805
  constructor(options = {}) {
2806
+ /**
2807
+ * Per-check-cycle result cache. `checkEnvironment()` clears it before
2808
+ * probing so a fresh cycle never returns stale results, while concurrent
2809
+ * or repeated `checkTool()` calls within the same cycle share one probe
2810
+ * (deduplicating expensive login-shell fallbacks).
2811
+ */
2812
+ this.toolCheckCache = /* @__PURE__ */ new Map();
2765
2813
  this.runCommandFn = options.runCommand ?? runCommand;
2766
2814
  this.platform = options.platform ?? process.platform;
2767
2815
  this.shell = options.shell ?? process.env.SHELL;
2768
2816
  }
2769
2817
  async checkEnvironment() {
2818
+ this.toolCheckCache.clear();
2770
2819
  const results = await Promise.all(
2771
2820
  import_devtools_protocol4.KNOWN_ENVIRONMENT_TOOLS.map(async (tool) => [tool, await this.checkTool(tool)])
2772
2821
  );
@@ -2776,6 +2825,15 @@ var EnvironmentInspector = class {
2776
2825
  if (!/^[a-zA-Z0-9-]+$/.test(toolName)) {
2777
2826
  throw (0, import_devtools_protocol4.createServicemeError)("invalid_params", "Invalid tool name.");
2778
2827
  }
2828
+ const cached = this.toolCheckCache.get(toolName);
2829
+ if (cached) {
2830
+ return cached;
2831
+ }
2832
+ const check = this.performToolCheck(toolName);
2833
+ this.toolCheckCache.set(toolName, check);
2834
+ return check;
2835
+ }
2836
+ async performToolCheck(toolName) {
2779
2837
  try {
2780
2838
  if (toolName === "nvm") {
2781
2839
  return await this.checkNvm();
@@ -2887,6 +2945,12 @@ var EnvironmentInspector = class {
2887
2945
  }
2888
2946
  }
2889
2947
  try {
2948
+ if (!this.resolveNvmShPath()) {
2949
+ return {
2950
+ installed: false,
2951
+ error: "Not installed"
2952
+ };
2953
+ }
2890
2954
  const nvmArgs = posixLoginShellArgs(this.platform, this.shell, "nvm --version");
2891
2955
  const result = await this.runCommandFn(nvmArgs.command, {
2892
2956
  args: nvmArgs.args,
@@ -2904,6 +2968,28 @@ var EnvironmentInspector = class {
2904
2968
  };
2905
2969
  }
2906
2970
  }
2971
+ /**
2972
+ * Resolve `nvm.sh` from `$NVM_DIR` and the known install roots without
2973
+ * spawning a shell. Returns undefined when nvm is not installed in any
2974
+ * well-known location, letting callers skip a login-shell probe.
2975
+ */
2976
+ resolveNvmShPath() {
2977
+ const candidates = [];
2978
+ const nvmDir = process.env.NVM_DIR?.trim();
2979
+ if (nvmDir && nvmDir.length > 0) {
2980
+ candidates.push(nvmDir);
2981
+ }
2982
+ for (const dir of NVM_FALLBACK_DIRS) {
2983
+ candidates.push(dir.replace(/^\$HOME/, (0, import_node_os.homedir)()));
2984
+ }
2985
+ for (const candidate of candidates) {
2986
+ const scriptPath = (0, import_node_path.join)(candidate, "nvm.sh");
2987
+ if ((0, import_node_fs.existsSync)(scriptPath)) {
2988
+ return scriptPath;
2989
+ }
2990
+ }
2991
+ return void 0;
2992
+ }
2907
2993
  async checkNuget() {
2908
2994
  const dotnetPath = await this.getToolPath("dotnet");
2909
2995
  if (!dotnetPath) {
@@ -3611,9 +3697,9 @@ var path11 = __toESM(require("path"));
3611
3697
  var import_devtools_protocol7 = require("@serviceme/devtools-protocol");
3612
3698
 
3613
3699
  // src/utils/fileUtils.ts
3614
- var import_node_fs = require("fs");
3700
+ var import_node_fs2 = require("fs");
3615
3701
  var import_promises3 = require("fs/promises");
3616
- var import_node_path = require("path");
3702
+ var import_node_path2 = require("path");
3617
3703
  var import_yauzl = __toESM(require("yauzl"));
3618
3704
  var unzipFile = (zipPath, dest) => {
3619
3705
  return new Promise((resolve3, reject) => {
@@ -3623,18 +3709,18 @@ var unzipFile = (zipPath, dest) => {
3623
3709
  zipfile.readEntry();
3624
3710
  zipfile.on("entry", (entry) => {
3625
3711
  if (/\/$/.test(entry.fileName)) {
3626
- void (0, import_promises3.mkdir)((0, import_node_path.join)(dest, entry.fileName), { recursive: true }).then(() => {
3712
+ void (0, import_promises3.mkdir)((0, import_node_path2.join)(dest, entry.fileName), { recursive: true }).then(() => {
3627
3713
  zipfile.readEntry();
3628
3714
  }).catch(reject);
3629
3715
  } else {
3630
- const outputPath = (0, import_node_path.join)(dest, entry.fileName);
3631
- void (0, import_promises3.mkdir)((0, import_node_path.dirname)(outputPath), { recursive: true }).then(() => {
3716
+ const outputPath = (0, import_node_path2.join)(dest, entry.fileName);
3717
+ void (0, import_promises3.mkdir)((0, import_node_path2.dirname)(outputPath), { recursive: true }).then(() => {
3632
3718
  zipfile.openReadStream(
3633
3719
  entry,
3634
3720
  (streamError, readStream) => {
3635
3721
  if (streamError) return reject(streamError);
3636
3722
  if (!readStream) return reject(new Error("Failed to open zip entry stream."));
3637
- const writeStream = (0, import_node_fs.createWriteStream)(outputPath);
3723
+ const writeStream = (0, import_node_fs2.createWriteStream)(outputPath);
3638
3724
  readStream.on("error", reject);
3639
3725
  writeStream.on("error", reject);
3640
3726
  writeStream.on("close", () => {
@@ -3676,7 +3762,7 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
3676
3762
  await (0, import_promises3.mkdir)(destPath, { recursive: true });
3677
3763
  const children = await (0, import_promises3.readdir)(sourcePath);
3678
3764
  for (const child of children) {
3679
- await mergeEntry((0, import_node_path.join)(sourcePath, child), (0, import_node_path.join)(destPath, child), overwrite);
3765
+ await mergeEntry((0, import_node_path2.join)(sourcePath, child), (0, import_node_path2.join)(destPath, child), overwrite);
3680
3766
  }
3681
3767
  await (0, import_promises3.rm)(sourcePath, { recursive: true, force: true });
3682
3768
  return;
@@ -3699,11 +3785,11 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
3699
3785
  await (0, import_promises3.mkdir)(destDir, { recursive: true });
3700
3786
  const files = await (0, import_promises3.readdir)(sourceDir);
3701
3787
  for (const file of files) {
3702
- const sourceFile = (0, import_node_path.join)(sourceDir, file);
3703
- const destFile = (0, import_node_path.join)(destDir, file);
3788
+ const sourceFile = (0, import_node_path2.join)(sourceDir, file);
3789
+ const destFile = (0, import_node_path2.join)(destDir, file);
3704
3790
  if (!overwrite) {
3705
3791
  try {
3706
- await (0, import_promises3.access)(destFile, import_node_fs.constants.F_OK);
3792
+ await (0, import_promises3.access)(destFile, import_node_fs2.constants.F_OK);
3707
3793
  await (0, import_promises3.rm)(sourceFile, { recursive: true, force: true });
3708
3794
  continue;
3709
3795
  } catch {