@serviceme/devtools-core 0.3.3 → 0.4.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/dist/index.mjs CHANGED
@@ -1994,6 +1994,7 @@ var FILE_MODE = 384;
1994
1994
  var LOCK_DIR_MODE = 448;
1995
1995
  var DEFAULT_LOCK_TIMEOUT_MS = 5e3;
1996
1996
  var DEFAULT_LOCK_RETRY_MS = 25;
1997
+ var LOCK_STALE_GRACE_MS = 200;
1997
1998
  var TMP_SUFFIX = ".tmp";
1998
1999
  var FsIdentityFileBackend = class {
1999
2000
  async exists(filePath) {
@@ -2098,14 +2099,20 @@ var FileLock = class {
2098
2099
  }
2099
2100
  }
2100
2101
  async isStaleLock() {
2102
+ let pidStr;
2101
2103
  try {
2102
- const pidStr = await fsp.readFile(this.pidFilePath, "utf8");
2103
- const pid = Number.parseInt(pidStr.trim(), 10);
2104
- if (!Number.isFinite(pid) || pid <= 0) return true;
2105
- return !isProcessAlive(pid);
2104
+ pidStr = await fsp.readFile(this.pidFilePath, "utf8");
2106
2105
  } catch {
2107
- return true;
2106
+ try {
2107
+ const stat7 = await fsp.stat(this.dirPath);
2108
+ return Date.now() - stat7.mtimeMs > LOCK_STALE_GRACE_MS;
2109
+ } catch {
2110
+ return false;
2111
+ }
2108
2112
  }
2113
+ const pid = Number.parseInt(pidStr.trim(), 10);
2114
+ if (!Number.isFinite(pid) || pid <= 0) return true;
2115
+ return !isProcessAlive(pid);
2109
2116
  }
2110
2117
  async release() {
2111
2118
  if (!this.acquired) return;
@@ -2405,9 +2412,9 @@ var DraftsStore = class {
2405
2412
  const dir = resolveDraftDir(kind, id);
2406
2413
  const manifestFilename = kind === "skill" ? "SKILL.md" : "AGENT.md";
2407
2414
  const manifestPath = path6.join(dir, manifestFilename);
2408
- let stat5;
2415
+ let stat7;
2409
2416
  try {
2410
- stat5 = await fs4.stat(manifestPath);
2417
+ stat7 = await fs4.stat(manifestPath);
2411
2418
  } catch {
2412
2419
  return null;
2413
2420
  }
@@ -2426,7 +2433,7 @@ var DraftsStore = class {
2426
2433
  dir,
2427
2434
  name,
2428
2435
  description,
2429
- modifiedAt: stat5.mtime.toISOString()
2436
+ modifiedAt: stat7.mtime.toISOString()
2430
2437
  };
2431
2438
  }
2432
2439
  /** Single draft detail (summary + all files). Throws when missing. */
@@ -2545,10 +2552,33 @@ var POSIX_LOGIN_SHELL_FALLBACK_TOOLS = /* @__PURE__ */ new Set([
2545
2552
  ]);
2546
2553
  var ERROR_CODE_NOT_FOUND = 127;
2547
2554
  var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
2555
+ var NVM_FALLBACK_DIRS = [
2556
+ "/opt/homebrew/opt/nvm",
2557
+ "/usr/local/opt/nvm",
2558
+ "$HOME/.nvm"
2559
+ ];
2560
+ function resolveUserLoginShell(platform3, envShell) {
2561
+ const shell = envShell?.trim();
2562
+ if (shell && shell.length > 0) {
2563
+ return shell;
2564
+ }
2565
+ return platform3 === "darwin" ? "/bin/zsh" : "/bin/bash";
2566
+ }
2567
+ function buildNvmSourcingSnippet() {
2568
+ const fallbackList = NVM_FALLBACK_DIRS.map((dir) => `"${dir}"`).join(" ");
2569
+ return `for d in $NVM_DIR ${fallbackList}; do [ -n "$d" ] && [ -s "$d/nvm.sh" ] && export NVM_DIR="$d" && . "$d/nvm.sh" && break; done`;
2570
+ }
2571
+ function posixLoginShellArgs(platform3, envShell, command) {
2572
+ return {
2573
+ command: resolveUserLoginShell(platform3, envShell),
2574
+ args: ["-lc", `${buildNvmSourcingSnippet()}; ${command}`]
2575
+ };
2576
+ }
2548
2577
  var EnvironmentInspector = class {
2549
2578
  constructor(options = {}) {
2550
2579
  this.runCommandFn = options.runCommand ?? runCommand;
2551
2580
  this.platform = options.platform ?? process.platform;
2581
+ this.shell = options.shell ?? process.env.SHELL;
2552
2582
  }
2553
2583
  async checkEnvironment() {
2554
2584
  const results = await Promise.all(
@@ -2595,11 +2625,9 @@ var EnvironmentInspector = class {
2595
2625
  }
2596
2626
  async getToolPathFromLoginShell(toolName) {
2597
2627
  try {
2598
- const result = await this.runCommandFn("/bin/bash", {
2599
- args: [
2600
- "-lc",
2601
- 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; command -v ' + toolName
2602
- ],
2628
+ const spawnArgs = posixLoginShellArgs(this.platform, this.shell, `command -v ${toolName}`);
2629
+ const result = await this.runCommandFn(spawnArgs.command, {
2630
+ args: spawnArgs.args,
2603
2631
  timeoutMs: this.getToolTimeout(toolName)
2604
2632
  });
2605
2633
  return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
@@ -2638,11 +2666,9 @@ var EnvironmentInspector = class {
2638
2666
  if (!this.shouldUsePosixLoginShellFallback(toolName)) {
2639
2667
  throw error;
2640
2668
  }
2641
- const fallbackResult = await this.runCommandFn("/bin/bash", {
2642
- args: [
2643
- "-lc",
2644
- `export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; ${toolName} --version`
2645
- ],
2669
+ const fallbackArgs = posixLoginShellArgs(this.platform, this.shell, `${toolName} --version`);
2670
+ const fallbackResult = await this.runCommandFn(fallbackArgs.command, {
2671
+ args: fallbackArgs.args,
2646
2672
  timeoutMs: this.getToolTimeout(toolName)
2647
2673
  });
2648
2674
  const fallbackOutput = (fallbackResult.stdout || fallbackResult.stderr).trim();
@@ -2675,11 +2701,9 @@ var EnvironmentInspector = class {
2675
2701
  }
2676
2702
  }
2677
2703
  try {
2678
- const result = await this.runCommandFn("/bin/bash", {
2679
- args: [
2680
- "-lc",
2681
- 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm --version'
2682
- ],
2704
+ const nvmArgs = posixLoginShellArgs(this.platform, this.shell, "nvm --version");
2705
+ const result = await this.runCommandFn(nvmArgs.command, {
2706
+ args: nvmArgs.args,
2683
2707
  timeoutMs: this.getToolTimeout("nvm")
2684
2708
  });
2685
2709
  return {
@@ -2866,7 +2890,9 @@ var GitClient = class {
2866
2890
  }
2867
2891
  const targetUrl = useProxy ? remoteUrl : originalUrl ?? remoteUrl;
2868
2892
  const proxyUrl = this.rewriteRemoteUrl(repoId, targetUrl, useProxy);
2869
- await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], { cwd: localPath });
2893
+ await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], {
2894
+ cwd: localPath
2895
+ });
2870
2896
  const before = await this.safeRevParse(localPath);
2871
2897
  await this.runOrThrow(["fetch", "origin"], { cwd: localPath });
2872
2898
  const after = await this.safeRevParse(localPath);
@@ -2902,7 +2928,9 @@ var GitClient = class {
2902
2928
  throw new Error(`no 'origin' remote configured at ${localPath}`);
2903
2929
  }
2904
2930
  const proxyUrl = this.rewriteRemoteUrl(repoId, remoteUrl, useProxy);
2905
- await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], { cwd: localPath });
2931
+ await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], {
2932
+ cwd: localPath
2933
+ });
2906
2934
  const result = await this.spawner.spawn(
2907
2935
  ["push", "origin", `refs/heads/${branch}:refs/heads/${branch}`],
2908
2936
  { cwd: localPath }
@@ -2956,7 +2984,9 @@ var GitClient = class {
2956
2984
  return { commitSha: sha };
2957
2985
  }
2958
2986
  async runOrThrow(args, opts) {
2959
- const result = await this.spawner.spawn(args, { cwd: opts.cwd ?? process.cwd() });
2987
+ const result = await this.spawner.spawn(args, {
2988
+ cwd: opts.cwd ?? process.cwd()
2989
+ });
2960
2990
  if (result.code !== 0) {
2961
2991
  throw new GitError(args, result);
2962
2992
  }
@@ -2971,7 +3001,9 @@ var GitClient = class {
2971
3001
  return url.length > 0 ? url : void 0;
2972
3002
  }
2973
3003
  async safeRevParse(localPath) {
2974
- const result = await this.spawner.spawn(["rev-parse", "HEAD"], { cwd: localPath });
3004
+ const result = await this.spawner.spawn(["rev-parse", "HEAD"], {
3005
+ cwd: localPath
3006
+ });
2975
3007
  if (result.code !== 0) return "";
2976
3008
  return result.stdout.trim();
2977
3009
  }
@@ -3018,7 +3050,11 @@ var StubGitSpawner = class {
3018
3050
  this.calls.push({ args, cwd: opts.cwd });
3019
3051
  const next = this.script.shift();
3020
3052
  if (!next) {
3021
- return { stdout: "", stderr: `stub: no scripted response for ${args.join(" ")}`, code: 1 };
3053
+ return {
3054
+ stdout: "",
3055
+ stderr: `stub: no scripted response for ${args.join(" ")}`,
3056
+ code: 1
3057
+ };
3022
3058
  }
3023
3059
  return next;
3024
3060
  }
@@ -3030,6 +3066,10 @@ function toFileUrl(absolutePath) {
3030
3066
  }
3031
3067
  return `file://${normalized}`;
3032
3068
  }
3069
+ var GIT_PROXY_PATH_SUFFIX = "/git-proxy";
3070
+ function buildGitProxyBase(serverBaseUrl) {
3071
+ return `${serverBaseUrl.replace(/\/+$/, "")}${GIT_PROXY_PATH_SUFFIX}`;
3072
+ }
3033
3073
 
3034
3074
  // src/image/imageTools.ts
3035
3075
  import * as fs5 from "fs/promises";
@@ -4671,13 +4711,13 @@ var PidManager = class {
4671
4711
  fs13.writeFileSync(this.pidPath, String(pid), "utf-8");
4672
4712
  }
4673
4713
  readPid() {
4674
- let stat5;
4714
+ let stat7;
4675
4715
  try {
4676
- stat5 = fs13.statSync(this.pidPath);
4716
+ stat7 = fs13.statSync(this.pidPath);
4677
4717
  } catch {
4678
4718
  return null;
4679
4719
  }
4680
- if (!stat5.isFile()) return null;
4720
+ if (!stat7.isFile()) return null;
4681
4721
  const raw = fs13.readFileSync(this.pidPath, "utf-8").trim();
4682
4722
  const pid = Number.parseInt(raw, 10);
4683
4723
  return Number.isNaN(pid) ? null : pid;
@@ -6814,6 +6854,7 @@ var FILE_MODE2 = 384;
6814
6854
  var LOCK_DIR_MODE2 = 448;
6815
6855
  var DEFAULT_LOCK_TIMEOUT_MS2 = 5e3;
6816
6856
  var DEFAULT_LOCK_RETRY_MS2 = 25;
6857
+ var LOCK_STALE_GRACE_MS2 = 200;
6817
6858
  var TMP_SUFFIX2 = ".tmp";
6818
6859
  var WORKSPACE_TOOLBOX_RELATIVE_PATH = path25.join(".github", ".serviceme-toolbox.json");
6819
6860
  var LEGACY_WORKSPACE_TOOLBOX_FILENAME = ".ms-devtools-toolbox.json";
@@ -6832,6 +6873,9 @@ async function migrateLegacyWorkspaceToolboxFile(filePath) {
6832
6873
  }
6833
6874
  }
6834
6875
  var FsToolboxFileBackend = class {
6876
+ constructor() {
6877
+ this.maxBackupCount = 5;
6878
+ }
6835
6879
  async exists(filePath) {
6836
6880
  try {
6837
6881
  await fsp2.access(filePath);
@@ -6860,9 +6904,28 @@ var FsToolboxFileBackend = class {
6860
6904
  try {
6861
6905
  const backupPath = `${filePath}.corrupted.${Date.now()}.bak`;
6862
6906
  await fsp2.copyFile(filePath, backupPath);
6907
+ await this.purgeExcessBackups(filePath);
6863
6908
  } catch {
6864
6909
  }
6865
6910
  }
6911
+ async purgeExcessBackups(filePath) {
6912
+ const dir = path25.dirname(filePath);
6913
+ const base = path25.basename(filePath);
6914
+ let entries;
6915
+ try {
6916
+ entries = await fsp2.readdir(dir);
6917
+ } catch {
6918
+ return;
6919
+ }
6920
+ const backups = entries.filter((n) => n.startsWith(base) && n.endsWith(".bak")).map((n) => ({ name: n, filePath: path25.join(dir, n) })).sort((a, b) => {
6921
+ return a.name.localeCompare(b.name);
6922
+ });
6923
+ const excess = backups.length - this.maxBackupCount;
6924
+ if (excess <= 0) return;
6925
+ await Promise.all(
6926
+ backups.slice(0, excess).map((b) => fsp2.rm(b.filePath).catch(() => void 0))
6927
+ );
6928
+ }
6866
6929
  async write(filePath, payload) {
6867
6930
  await fsp2.mkdir(path25.dirname(filePath), { recursive: true });
6868
6931
  const tmpPath = `${filePath}${TMP_SUFFIX2}`;
@@ -6935,14 +6998,20 @@ var ToolboxFileLock = class {
6935
6998
  }
6936
6999
  }
6937
7000
  async isStaleLock() {
7001
+ let pidStr;
6938
7002
  try {
6939
- const pidStr = await fsp2.readFile(this.pidFilePath, "utf8");
6940
- const pid = Number.parseInt(pidStr.trim(), 10);
6941
- if (!Number.isFinite(pid) || pid <= 0) return true;
6942
- return !isProcessAlive2(pid);
7003
+ pidStr = await fsp2.readFile(this.pidFilePath, "utf8");
6943
7004
  } catch {
6944
- return true;
7005
+ try {
7006
+ const stat7 = await fsp2.stat(this.dirPath);
7007
+ return Date.now() - stat7.mtimeMs > LOCK_STALE_GRACE_MS2;
7008
+ } catch {
7009
+ return false;
7010
+ }
6945
7011
  }
7012
+ const pid = Number.parseInt(pidStr.trim(), 10);
7013
+ if (!Number.isFinite(pid) || pid <= 0) return true;
7014
+ return !isProcessAlive2(pid);
6946
7015
  }
6947
7016
  async release() {
6948
7017
  if (!this.acquired) return;
@@ -7222,6 +7291,7 @@ export {
7222
7291
  EnvironmentInspector,
7223
7292
  FsIdentityFileBackend,
7224
7293
  FsToolboxFileBackend,
7294
+ GIT_PROXY_PATH_SUFFIX,
7225
7295
  GitClient,
7226
7296
  GitError,
7227
7297
  GitHubAuthProvider,
@@ -7288,6 +7358,7 @@ export {
7288
7358
  bootstrapPhase5Placeholders,
7289
7359
  buildDefaultReposFile,
7290
7360
  buildGitHubLocalEmail,
7361
+ buildGitProxyBase,
7291
7362
  buildSignedHeaders,
7292
7363
  copilotDoctor,
7293
7364
  copilotPrompt,