@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.js CHANGED
@@ -59,6 +59,7 @@ __export(src_exports, {
59
59
  EnvironmentInspector: () => EnvironmentInspector,
60
60
  FsIdentityFileBackend: () => FsIdentityFileBackend,
61
61
  FsToolboxFileBackend: () => FsToolboxFileBackend,
62
+ GIT_PROXY_PATH_SUFFIX: () => GIT_PROXY_PATH_SUFFIX,
62
63
  GitClient: () => GitClient,
63
64
  GitError: () => GitError,
64
65
  GitHubAuthProvider: () => GitHubAuthProvider,
@@ -125,6 +126,7 @@ __export(src_exports, {
125
126
  bootstrapPhase5Placeholders: () => bootstrapPhase5Placeholders,
126
127
  buildDefaultReposFile: () => buildDefaultReposFile,
127
128
  buildGitHubLocalEmail: () => buildGitHubLocalEmail,
129
+ buildGitProxyBase: () => buildGitProxyBase,
128
130
  buildSignedHeaders: () => buildSignedHeaders,
129
131
  copilotDoctor: () => copilotDoctor,
130
132
  copilotPrompt: () => copilotPrompt,
@@ -2185,6 +2187,7 @@ var FILE_MODE = 384;
2185
2187
  var LOCK_DIR_MODE = 448;
2186
2188
  var DEFAULT_LOCK_TIMEOUT_MS = 5e3;
2187
2189
  var DEFAULT_LOCK_RETRY_MS = 25;
2190
+ var LOCK_STALE_GRACE_MS = 200;
2188
2191
  var TMP_SUFFIX = ".tmp";
2189
2192
  var FsIdentityFileBackend = class {
2190
2193
  async exists(filePath) {
@@ -2289,14 +2292,20 @@ var FileLock = class {
2289
2292
  }
2290
2293
  }
2291
2294
  async isStaleLock() {
2295
+ let pidStr;
2292
2296
  try {
2293
- const pidStr = await fsp.readFile(this.pidFilePath, "utf8");
2294
- const pid = Number.parseInt(pidStr.trim(), 10);
2295
- if (!Number.isFinite(pid) || pid <= 0) return true;
2296
- return !isProcessAlive(pid);
2297
+ pidStr = await fsp.readFile(this.pidFilePath, "utf8");
2297
2298
  } catch {
2298
- return true;
2299
+ try {
2300
+ const stat7 = await fsp.stat(this.dirPath);
2301
+ return Date.now() - stat7.mtimeMs > LOCK_STALE_GRACE_MS;
2302
+ } catch {
2303
+ return false;
2304
+ }
2299
2305
  }
2306
+ const pid = Number.parseInt(pidStr.trim(), 10);
2307
+ if (!Number.isFinite(pid) || pid <= 0) return true;
2308
+ return !isProcessAlive(pid);
2300
2309
  }
2301
2310
  async release() {
2302
2311
  if (!this.acquired) return;
@@ -2596,9 +2605,9 @@ var DraftsStore = class {
2596
2605
  const dir = resolveDraftDir(kind, id);
2597
2606
  const manifestFilename = kind === "skill" ? "SKILL.md" : "AGENT.md";
2598
2607
  const manifestPath = path6.join(dir, manifestFilename);
2599
- let stat5;
2608
+ let stat7;
2600
2609
  try {
2601
- stat5 = await fs4.stat(manifestPath);
2610
+ stat7 = await fs4.stat(manifestPath);
2602
2611
  } catch {
2603
2612
  return null;
2604
2613
  }
@@ -2617,7 +2626,7 @@ var DraftsStore = class {
2617
2626
  dir,
2618
2627
  name,
2619
2628
  description,
2620
- modifiedAt: stat5.mtime.toISOString()
2629
+ modifiedAt: stat7.mtime.toISOString()
2621
2630
  };
2622
2631
  }
2623
2632
  /** Single draft detail (summary + all files). Throws when missing. */
@@ -2733,10 +2742,33 @@ var POSIX_LOGIN_SHELL_FALLBACK_TOOLS = /* @__PURE__ */ new Set([
2733
2742
  ]);
2734
2743
  var ERROR_CODE_NOT_FOUND = 127;
2735
2744
  var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
2745
+ var NVM_FALLBACK_DIRS = [
2746
+ "/opt/homebrew/opt/nvm",
2747
+ "/usr/local/opt/nvm",
2748
+ "$HOME/.nvm"
2749
+ ];
2750
+ function resolveUserLoginShell(platform3, envShell) {
2751
+ const shell = envShell?.trim();
2752
+ if (shell && shell.length > 0) {
2753
+ return shell;
2754
+ }
2755
+ return platform3 === "darwin" ? "/bin/zsh" : "/bin/bash";
2756
+ }
2757
+ function buildNvmSourcingSnippet() {
2758
+ const fallbackList = NVM_FALLBACK_DIRS.map((dir) => `"${dir}"`).join(" ");
2759
+ return `for d in $NVM_DIR ${fallbackList}; do [ -n "$d" ] && [ -s "$d/nvm.sh" ] && export NVM_DIR="$d" && . "$d/nvm.sh" && break; done`;
2760
+ }
2761
+ function posixLoginShellArgs(platform3, envShell, command) {
2762
+ return {
2763
+ command: resolveUserLoginShell(platform3, envShell),
2764
+ args: ["-lc", `${buildNvmSourcingSnippet()}; ${command}`]
2765
+ };
2766
+ }
2736
2767
  var EnvironmentInspector = class {
2737
2768
  constructor(options = {}) {
2738
2769
  this.runCommandFn = options.runCommand ?? runCommand;
2739
2770
  this.platform = options.platform ?? process.platform;
2771
+ this.shell = options.shell ?? process.env.SHELL;
2740
2772
  }
2741
2773
  async checkEnvironment() {
2742
2774
  const results = await Promise.all(
@@ -2783,11 +2815,9 @@ var EnvironmentInspector = class {
2783
2815
  }
2784
2816
  async getToolPathFromLoginShell(toolName) {
2785
2817
  try {
2786
- const result = await this.runCommandFn("/bin/bash", {
2787
- args: [
2788
- "-lc",
2789
- 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; command -v ' + toolName
2790
- ],
2818
+ const spawnArgs = posixLoginShellArgs(this.platform, this.shell, `command -v ${toolName}`);
2819
+ const result = await this.runCommandFn(spawnArgs.command, {
2820
+ args: spawnArgs.args,
2791
2821
  timeoutMs: this.getToolTimeout(toolName)
2792
2822
  });
2793
2823
  return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
@@ -2826,11 +2856,9 @@ var EnvironmentInspector = class {
2826
2856
  if (!this.shouldUsePosixLoginShellFallback(toolName)) {
2827
2857
  throw error;
2828
2858
  }
2829
- const fallbackResult = await this.runCommandFn("/bin/bash", {
2830
- args: [
2831
- "-lc",
2832
- `export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; ${toolName} --version`
2833
- ],
2859
+ const fallbackArgs = posixLoginShellArgs(this.platform, this.shell, `${toolName} --version`);
2860
+ const fallbackResult = await this.runCommandFn(fallbackArgs.command, {
2861
+ args: fallbackArgs.args,
2834
2862
  timeoutMs: this.getToolTimeout(toolName)
2835
2863
  });
2836
2864
  const fallbackOutput = (fallbackResult.stdout || fallbackResult.stderr).trim();
@@ -2863,11 +2891,9 @@ var EnvironmentInspector = class {
2863
2891
  }
2864
2892
  }
2865
2893
  try {
2866
- const result = await this.runCommandFn("/bin/bash", {
2867
- args: [
2868
- "-lc",
2869
- 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm --version'
2870
- ],
2894
+ const nvmArgs = posixLoginShellArgs(this.platform, this.shell, "nvm --version");
2895
+ const result = await this.runCommandFn(nvmArgs.command, {
2896
+ args: nvmArgs.args,
2871
2897
  timeoutMs: this.getToolTimeout("nvm")
2872
2898
  });
2873
2899
  return {
@@ -3054,7 +3080,9 @@ var GitClient = class {
3054
3080
  }
3055
3081
  const targetUrl = useProxy ? remoteUrl : originalUrl ?? remoteUrl;
3056
3082
  const proxyUrl = this.rewriteRemoteUrl(repoId, targetUrl, useProxy);
3057
- await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], { cwd: localPath });
3083
+ await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], {
3084
+ cwd: localPath
3085
+ });
3058
3086
  const before = await this.safeRevParse(localPath);
3059
3087
  await this.runOrThrow(["fetch", "origin"], { cwd: localPath });
3060
3088
  const after = await this.safeRevParse(localPath);
@@ -3090,7 +3118,9 @@ var GitClient = class {
3090
3118
  throw new Error(`no 'origin' remote configured at ${localPath}`);
3091
3119
  }
3092
3120
  const proxyUrl = this.rewriteRemoteUrl(repoId, remoteUrl, useProxy);
3093
- await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], { cwd: localPath });
3121
+ await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], {
3122
+ cwd: localPath
3123
+ });
3094
3124
  const result = await this.spawner.spawn(
3095
3125
  ["push", "origin", `refs/heads/${branch}:refs/heads/${branch}`],
3096
3126
  { cwd: localPath }
@@ -3144,7 +3174,9 @@ var GitClient = class {
3144
3174
  return { commitSha: sha };
3145
3175
  }
3146
3176
  async runOrThrow(args, opts) {
3147
- const result = await this.spawner.spawn(args, { cwd: opts.cwd ?? process.cwd() });
3177
+ const result = await this.spawner.spawn(args, {
3178
+ cwd: opts.cwd ?? process.cwd()
3179
+ });
3148
3180
  if (result.code !== 0) {
3149
3181
  throw new GitError(args, result);
3150
3182
  }
@@ -3159,7 +3191,9 @@ var GitClient = class {
3159
3191
  return url.length > 0 ? url : void 0;
3160
3192
  }
3161
3193
  async safeRevParse(localPath) {
3162
- const result = await this.spawner.spawn(["rev-parse", "HEAD"], { cwd: localPath });
3194
+ const result = await this.spawner.spawn(["rev-parse", "HEAD"], {
3195
+ cwd: localPath
3196
+ });
3163
3197
  if (result.code !== 0) return "";
3164
3198
  return result.stdout.trim();
3165
3199
  }
@@ -3206,7 +3240,11 @@ var StubGitSpawner = class {
3206
3240
  this.calls.push({ args, cwd: opts.cwd });
3207
3241
  const next = this.script.shift();
3208
3242
  if (!next) {
3209
- return { stdout: "", stderr: `stub: no scripted response for ${args.join(" ")}`, code: 1 };
3243
+ return {
3244
+ stdout: "",
3245
+ stderr: `stub: no scripted response for ${args.join(" ")}`,
3246
+ code: 1
3247
+ };
3210
3248
  }
3211
3249
  return next;
3212
3250
  }
@@ -3218,6 +3256,10 @@ function toFileUrl(absolutePath) {
3218
3256
  }
3219
3257
  return `file://${normalized}`;
3220
3258
  }
3259
+ var GIT_PROXY_PATH_SUFFIX = "/git-proxy";
3260
+ function buildGitProxyBase(serverBaseUrl) {
3261
+ return `${serverBaseUrl.replace(/\/+$/, "")}${GIT_PROXY_PATH_SUFFIX}`;
3262
+ }
3221
3263
 
3222
3264
  // src/image/imageTools.ts
3223
3265
  var fs5 = __toESM(require("fs/promises"));
@@ -4852,13 +4894,13 @@ var PidManager = class {
4852
4894
  fs13.writeFileSync(this.pidPath, String(pid), "utf-8");
4853
4895
  }
4854
4896
  readPid() {
4855
- let stat5;
4897
+ let stat7;
4856
4898
  try {
4857
- stat5 = fs13.statSync(this.pidPath);
4899
+ stat7 = fs13.statSync(this.pidPath);
4858
4900
  } catch {
4859
4901
  return null;
4860
4902
  }
4861
- if (!stat5.isFile()) return null;
4903
+ if (!stat7.isFile()) return null;
4862
4904
  const raw = fs13.readFileSync(this.pidPath, "utf-8").trim();
4863
4905
  const pid = Number.parseInt(raw, 10);
4864
4906
  return Number.isNaN(pid) ? null : pid;
@@ -6989,6 +7031,7 @@ var FILE_MODE2 = 384;
6989
7031
  var LOCK_DIR_MODE2 = 448;
6990
7032
  var DEFAULT_LOCK_TIMEOUT_MS2 = 5e3;
6991
7033
  var DEFAULT_LOCK_RETRY_MS2 = 25;
7034
+ var LOCK_STALE_GRACE_MS2 = 200;
6992
7035
  var TMP_SUFFIX2 = ".tmp";
6993
7036
  var WORKSPACE_TOOLBOX_RELATIVE_PATH = path25.join(".github", ".serviceme-toolbox.json");
6994
7037
  var LEGACY_WORKSPACE_TOOLBOX_FILENAME = ".ms-devtools-toolbox.json";
@@ -7007,6 +7050,9 @@ async function migrateLegacyWorkspaceToolboxFile(filePath) {
7007
7050
  }
7008
7051
  }
7009
7052
  var FsToolboxFileBackend = class {
7053
+ constructor() {
7054
+ this.maxBackupCount = 5;
7055
+ }
7010
7056
  async exists(filePath) {
7011
7057
  try {
7012
7058
  await fsp2.access(filePath);
@@ -7035,9 +7081,28 @@ var FsToolboxFileBackend = class {
7035
7081
  try {
7036
7082
  const backupPath = `${filePath}.corrupted.${Date.now()}.bak`;
7037
7083
  await fsp2.copyFile(filePath, backupPath);
7084
+ await this.purgeExcessBackups(filePath);
7038
7085
  } catch {
7039
7086
  }
7040
7087
  }
7088
+ async purgeExcessBackups(filePath) {
7089
+ const dir = path25.dirname(filePath);
7090
+ const base = path25.basename(filePath);
7091
+ let entries;
7092
+ try {
7093
+ entries = await fsp2.readdir(dir);
7094
+ } catch {
7095
+ return;
7096
+ }
7097
+ const backups = entries.filter((n) => n.startsWith(base) && n.endsWith(".bak")).map((n) => ({ name: n, filePath: path25.join(dir, n) })).sort((a, b) => {
7098
+ return a.name.localeCompare(b.name);
7099
+ });
7100
+ const excess = backups.length - this.maxBackupCount;
7101
+ if (excess <= 0) return;
7102
+ await Promise.all(
7103
+ backups.slice(0, excess).map((b) => fsp2.rm(b.filePath).catch(() => void 0))
7104
+ );
7105
+ }
7041
7106
  async write(filePath, payload) {
7042
7107
  await fsp2.mkdir(path25.dirname(filePath), { recursive: true });
7043
7108
  const tmpPath = `${filePath}${TMP_SUFFIX2}`;
@@ -7110,14 +7175,20 @@ var ToolboxFileLock = class {
7110
7175
  }
7111
7176
  }
7112
7177
  async isStaleLock() {
7178
+ let pidStr;
7113
7179
  try {
7114
- const pidStr = await fsp2.readFile(this.pidFilePath, "utf8");
7115
- const pid = Number.parseInt(pidStr.trim(), 10);
7116
- if (!Number.isFinite(pid) || pid <= 0) return true;
7117
- return !isProcessAlive2(pid);
7180
+ pidStr = await fsp2.readFile(this.pidFilePath, "utf8");
7118
7181
  } catch {
7119
- return true;
7182
+ try {
7183
+ const stat7 = await fsp2.stat(this.dirPath);
7184
+ return Date.now() - stat7.mtimeMs > LOCK_STALE_GRACE_MS2;
7185
+ } catch {
7186
+ return false;
7187
+ }
7120
7188
  }
7189
+ const pid = Number.parseInt(pidStr.trim(), 10);
7190
+ if (!Number.isFinite(pid) || pid <= 0) return true;
7191
+ return !isProcessAlive2(pid);
7121
7192
  }
7122
7193
  async release() {
7123
7194
  if (!this.acquired) return;
@@ -7398,6 +7469,7 @@ var ToolboxCore = class {
7398
7469
  EnvironmentInspector,
7399
7470
  FsIdentityFileBackend,
7400
7471
  FsToolboxFileBackend,
7472
+ GIT_PROXY_PATH_SUFFIX,
7401
7473
  GitClient,
7402
7474
  GitError,
7403
7475
  GitHubAuthProvider,
@@ -7464,6 +7536,7 @@ var ToolboxCore = class {
7464
7536
  bootstrapPhase5Placeholders,
7465
7537
  buildDefaultReposFile,
7466
7538
  buildGitHubLocalEmail,
7539
+ buildGitProxyBase,
7467
7540
  buildSignedHeaders,
7468
7541
  copilotDoctor,
7469
7542
  copilotPrompt,