@serviceme/devtools-core 0.3.2 → 0.3.4

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
@@ -454,6 +454,50 @@ var AccessControl = class {
454
454
  }
455
455
  };
456
456
 
457
+ // src/logger.ts
458
+ var noopLogger = {
459
+ debug() {
460
+ },
461
+ info() {
462
+ },
463
+ warn() {
464
+ },
465
+ error() {
466
+ }
467
+ };
468
+ function formatArgs(args) {
469
+ return args.map((arg) => {
470
+ if (typeof arg === "string") {
471
+ return arg;
472
+ }
473
+ try {
474
+ return JSON.stringify(arg);
475
+ } catch {
476
+ return String(arg);
477
+ }
478
+ }).join(" ");
479
+ }
480
+ function createConsoleLogger(prefix = "serviceme") {
481
+ return {
482
+ debug(message, ...args) {
483
+ process.stderr.write(`[${prefix}] DEBUG ${message} ${formatArgs(args)}
484
+ `);
485
+ },
486
+ info(message, ...args) {
487
+ process.stderr.write(`[${prefix}] INFO ${message} ${formatArgs(args)}
488
+ `);
489
+ },
490
+ warn(message, ...args) {
491
+ process.stderr.write(`[${prefix}] WARN ${message} ${formatArgs(args)}
492
+ `);
493
+ },
494
+ error(message, ...args) {
495
+ process.stderr.write(`[${prefix}] ERROR ${message} ${formatArgs(args)}
496
+ `);
497
+ }
498
+ };
499
+ }
500
+
457
501
  // src/auth/AuthStateManager.ts
458
502
  import { EventEmitter } from "events";
459
503
  var AuthStateManager = class {
@@ -616,6 +660,7 @@ var AuthCore = class {
616
660
  this.state = opts.stateManager ?? new AuthStateManager();
617
661
  this.tokenStore = opts.tokenStore;
618
662
  this.accessControl = opts.accessControl;
663
+ this.logger = opts.logger ?? noopLogger;
619
664
  }
620
665
  /** Snapshot of every account, the active provider, and the last error. */
621
666
  status() {
@@ -637,16 +682,28 @@ var AuthCore = class {
637
682
  */
638
683
  async login(provider, ui, shouldContinue) {
639
684
  const providerImpl = this.registry.get(provider);
685
+ this.logger.info("[AuthCore] Starting device-flow login", { provider });
640
686
  try {
641
687
  const initial = await providerImpl.requestDeviceFlow();
642
688
  await ui(initial);
689
+ if (!initial.deviceCode) {
690
+ this.logger.error("[AuthCore] Provider did not return deviceCode", provider);
691
+ throw new Error("Auth provider did not return deviceCode for device flow completion");
692
+ }
643
693
  const session = await providerImpl.completeDeviceFlow(
644
- initial.userCode ? initial.deviceCode ?? "" : "",
645
- shouldContinue
694
+ initial.deviceCode,
695
+ shouldContinue,
696
+ initial.pollIntervalMs
646
697
  );
647
- return await this.persistSession(providerImpl, session);
698
+ const result = await this.persistSession(providerImpl, session);
699
+ this.logger.info("[AuthCore] Device-flow login completed", { provider });
700
+ return result;
648
701
  } catch (err) {
649
- this.state.recordError(err instanceof Error ? err.message : String(err));
702
+ const message = err instanceof Error ? err.message : String(err);
703
+ this.logger.error("[AuthCore] Device-flow login failed", message, {
704
+ provider
705
+ });
706
+ this.state.recordError(message);
650
707
  throw err;
651
708
  }
652
709
  }
@@ -671,7 +728,10 @@ var AuthCore = class {
671
728
  if (!provider) return null;
672
729
  const account = this.state.getActiveAccount();
673
730
  if (!account) return null;
674
- const envelope = await this.tokenStore.get({ provider, accountId: account.id });
731
+ const envelope = await this.tokenStore.get({
732
+ provider,
733
+ accountId: account.id
734
+ });
675
735
  if (!envelope) return null;
676
736
  return { provider, account, token: envelope.token };
677
737
  }
@@ -681,9 +741,15 @@ var AuthCore = class {
681
741
  * every account.
682
742
  */
683
743
  async logout(provider) {
744
+ this.logger.info("[AuthCore] Logout requested", {
745
+ provider: provider ?? "all"
746
+ });
684
747
  if (!provider) {
685
748
  for (const account of this.state.listAccounts()) {
686
- await this.tokenStore.delete({ provider: account.provider, accountId: account.id });
749
+ await this.tokenStore.delete({
750
+ provider: account.provider,
751
+ accountId: account.id
752
+ });
687
753
  this.state.removeAccount(account.provider, account.id);
688
754
  }
689
755
  this.state.clearAll();
@@ -758,6 +824,10 @@ var AuthCore = class {
758
824
  avatarUrl: meta.avatarUrl,
759
825
  expiresAt: meta.expiresAt ?? expiresAt
760
826
  };
827
+ this.logger.debug("[AuthCore] Persisting session", {
828
+ provider: meta.provider,
829
+ accountId: meta.id
830
+ });
761
831
  await this.tokenStore.set({ provider: meta.provider, accountId: meta.id }, session.token, {
762
832
  expiresAt
763
833
  });
@@ -860,11 +930,13 @@ var GitHubAuthProvider = class {
860
930
  userUrl: config.userUrl ?? DEFAULT_USER_URL,
861
931
  scope: config.scope ?? DEFAULT_SCOPE,
862
932
  minPollIntervalMs: config.minPollIntervalMs ?? 1e3,
863
- maxPollIntervalMs: config.maxPollIntervalMs ?? 15e3,
933
+ maxPollIntervalMs: config.maxPollIntervalMs ?? 6e4,
864
934
  maxWaitMs: config.maxWaitMs,
865
935
  fetchImpl: config.fetchImpl ?? fetch,
866
936
  deviceCodeRetryBaseDelayMs: config.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS
867
937
  };
938
+ this.logger = config.logger ?? noopLogger;
939
+ this.sleepImpl = config.sleepImpl ?? sleep;
868
940
  }
869
941
  async requestDeviceFlow(opts) {
870
942
  const body = JSON.stringify({
@@ -873,6 +945,10 @@ var GitHubAuthProvider = class {
873
945
  });
874
946
  let lastNetworkError;
875
947
  for (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {
948
+ this.logger.debug("[GitHubAuthProvider] Requesting device code", {
949
+ attempt,
950
+ maxAttempts: DEVICE_CODE_MAX_ATTEMPTS
951
+ });
876
952
  let resp;
877
953
  try {
878
954
  resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {
@@ -886,45 +962,99 @@ var GitHubAuthProvider = class {
886
962
  });
887
963
  } catch (error) {
888
964
  if (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {
965
+ this.logger.error(
966
+ "[GitHubAuthProvider] Device-code request failed (non-retryable)",
967
+ error instanceof Error ? error.message : String(error),
968
+ { attempt }
969
+ );
889
970
  throw error;
890
971
  }
972
+ const delayMs = this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1);
973
+ this.logger.warn(
974
+ "[GitHubAuthProvider] Transient network error requesting device code, retrying",
975
+ {
976
+ attempt,
977
+ delayMs,
978
+ error: error instanceof Error ? error.message : String(error)
979
+ }
980
+ );
891
981
  lastNetworkError = error;
892
- await sleep(this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1));
982
+ await this.sleepImpl(delayMs);
893
983
  continue;
894
984
  }
895
985
  if (!resp.ok) {
986
+ this.logger.error(
987
+ "[GitHubAuthProvider] Device-code request rejected by GitHub",
988
+ `HTTP ${resp.status}`
989
+ );
896
990
  throw new Error(`GitHub device-code request failed: HTTP ${resp.status}`);
897
991
  }
898
992
  const data = await resp.json();
899
993
  if (!data.device_code || !data.user_code || !data.verification_uri) {
994
+ this.logger.error(
995
+ "[GitHubAuthProvider] Device-code response missing required fields",
996
+ JSON.stringify(Object.keys(data))
997
+ );
900
998
  throw new Error("GitHub device-code response missing required fields");
901
999
  }
1000
+ this.logger.info("[GitHubAuthProvider] Device code obtained", {
1001
+ userCode: data.user_code,
1002
+ verificationUri: data.verification_uri,
1003
+ expiresInSec: data.expires_in,
1004
+ pollIntervalSec: data.interval
1005
+ });
902
1006
  return {
903
1007
  provider: this.providerId,
1008
+ deviceCode: data.device_code,
904
1009
  userCode: data.user_code,
905
1010
  verificationUrl: data.verification_uri,
906
1011
  expiresAt: Date.now() + data.expires_in * 1e3,
1012
+ pollIntervalMs: data.interval * 1e3,
907
1013
  message: `Open ${data.verification_uri} and enter ${data.user_code}`
908
1014
  };
909
1015
  }
910
1016
  throw lastNetworkError ?? new Error("GitHub device-code request failed");
911
1017
  }
912
- async completeDeviceFlow(deviceCode, shouldContinue) {
1018
+ async completeDeviceFlow(deviceCode, shouldContinue, initialPollIntervalMs) {
913
1019
  const start = Date.now();
914
- let pollIntervalMs = this.cfg.minPollIntervalMs;
1020
+ let pollIntervalMs = Math.max(this.cfg.minPollIntervalMs, initialPollIntervalMs ?? 0);
915
1021
  let consecutiveSlowDown = 0;
1022
+ let pollCount = 0;
1023
+ this.logger.info("[GitHubAuthProvider] Starting device-flow polling", {
1024
+ initialPollIntervalMs: pollIntervalMs,
1025
+ maxWaitMs: this.cfg.maxWaitMs
1026
+ });
916
1027
  while (true) {
917
1028
  if (shouldContinue && !shouldContinue()) {
1029
+ this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
1030
+ elapsedMs: Date.now() - start,
1031
+ pollCount
1032
+ });
918
1033
  throw new Error("GitHub device flow cancelled by caller");
919
1034
  }
920
1035
  const elapsed = Date.now() - start;
921
1036
  if (this.cfg.maxWaitMs !== void 0 && elapsed > this.cfg.maxWaitMs) {
1037
+ this.logger.warn("[GitHubAuthProvider] Device flow exceeded max wait time", {
1038
+ elapsedMs: elapsed,
1039
+ maxWaitMs: this.cfg.maxWaitMs,
1040
+ pollCount
1041
+ });
922
1042
  throw new Error("GitHub device flow exceeded max wait time");
923
1043
  }
924
- await sleep(pollIntervalMs);
1044
+ await this.sleepImpl(pollIntervalMs);
925
1045
  if (shouldContinue && !shouldContinue()) {
1046
+ this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
1047
+ elapsedMs: Date.now() - start,
1048
+ pollCount
1049
+ });
926
1050
  throw new Error("GitHub device flow cancelled by caller");
927
1051
  }
1052
+ pollCount++;
1053
+ this.logger.debug("[GitHubAuthProvider] Polling for authorization", {
1054
+ pollCount,
1055
+ elapsedMs: Date.now() - start,
1056
+ pollIntervalMs
1057
+ });
928
1058
  let resp;
929
1059
  try {
930
1060
  resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
@@ -942,19 +1072,59 @@ var GitHubAuthProvider = class {
942
1072
  });
943
1073
  } catch (error) {
944
1074
  if (!isTransientNetworkError(error)) {
1075
+ this.logger.error(
1076
+ "[GitHubAuthProvider] Non-retryable error while polling token endpoint",
1077
+ error instanceof Error ? error.message : String(error),
1078
+ { pollCount }
1079
+ );
945
1080
  throw error;
946
1081
  }
947
- pollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);
1082
+ const nextPollIntervalMs = Math.min(
1083
+ Math.round(pollIntervalMs * 1.5),
1084
+ this.cfg.maxPollIntervalMs
1085
+ );
1086
+ this.logger.warn(
1087
+ "[GitHubAuthProvider] Transient network error while polling, backing off",
1088
+ {
1089
+ pollCount,
1090
+ error: error instanceof Error ? error.message : String(error),
1091
+ previousPollIntervalMs: pollIntervalMs,
1092
+ nextPollIntervalMs
1093
+ }
1094
+ );
1095
+ pollIntervalMs = nextPollIntervalMs;
948
1096
  continue;
949
1097
  }
950
1098
  if (!resp.ok) {
951
- pollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);
1099
+ const nextPollIntervalMs = Math.min(
1100
+ Math.round(pollIntervalMs * 1.5),
1101
+ this.cfg.maxPollIntervalMs
1102
+ );
1103
+ this.logger.warn(
1104
+ "[GitHubAuthProvider] Token endpoint returned non-OK status, backing off",
1105
+ {
1106
+ pollCount,
1107
+ status: resp.status,
1108
+ previousPollIntervalMs: pollIntervalMs,
1109
+ nextPollIntervalMs
1110
+ }
1111
+ );
1112
+ pollIntervalMs = nextPollIntervalMs;
952
1113
  continue;
953
1114
  }
954
1115
  const data = await resp.json();
955
1116
  if (data.access_token) {
1117
+ this.logger.info("[GitHubAuthProvider] Authorization granted, fetching user profile", {
1118
+ pollCount,
1119
+ elapsedMs: Date.now() - start
1120
+ });
956
1121
  const rawUser = await this.fetchGitHubUser(data.access_token);
957
1122
  const email = await this.resolveEmail(data.access_token, rawUser);
1123
+ this.logger.info("[GitHubAuthProvider] Device-flow login completed", {
1124
+ pollCount,
1125
+ elapsedMs: Date.now() - start,
1126
+ login: rawUser.login
1127
+ });
958
1128
  return {
959
1129
  token: data.access_token,
960
1130
  refreshToken: data.refresh_token,
@@ -969,22 +1139,42 @@ var GitHubAuthProvider = class {
969
1139
  };
970
1140
  }
971
1141
  if (data.error === "authorization_pending") {
1142
+ this.logger.debug("[GitHubAuthProvider] Authorization still pending", {
1143
+ pollCount,
1144
+ elapsedMs: Date.now() - start
1145
+ });
972
1146
  continue;
973
1147
  }
974
1148
  if (data.error === "slow_down") {
975
1149
  consecutiveSlowDown++;
976
- pollIntervalMs = Math.min(
977
- Math.round(pollIntervalMs * 1.5 + Math.min(consecutiveSlowDown * 500, 2e3)),
978
- this.cfg.maxPollIntervalMs
979
- );
1150
+ const nextPollIntervalMs = Math.min(pollIntervalMs + 5e3, this.cfg.maxPollIntervalMs);
1151
+ this.logger.warn("[GitHubAuthProvider] GitHub requested slower polling (slow_down)", {
1152
+ pollCount,
1153
+ consecutiveSlowDown,
1154
+ previousPollIntervalMs: pollIntervalMs,
1155
+ nextPollIntervalMs
1156
+ });
1157
+ pollIntervalMs = nextPollIntervalMs;
980
1158
  continue;
981
1159
  }
982
1160
  if (data.error === "access_denied") {
1161
+ this.logger.warn("[GitHubAuthProvider] User denied authorization", {
1162
+ pollCount
1163
+ });
983
1164
  throw new Error("User denied authorization");
984
1165
  }
985
1166
  if (data.error === "expired_token") {
1167
+ this.logger.warn(
1168
+ "[GitHubAuthProvider] Device code expired before authorization completed",
1169
+ { pollCount, elapsedMs: Date.now() - start }
1170
+ );
986
1171
  throw new Error("GitHub device code expired \u2014 restart the flow");
987
1172
  }
1173
+ this.logger.error(
1174
+ "[GitHubAuthProvider] Unexpected device-flow error from token endpoint",
1175
+ data.error ?? "unknown",
1176
+ { pollCount }
1177
+ );
988
1178
  throw new Error(`GitHub device flow error: ${data.error ?? "unknown"}`);
989
1179
  }
990
1180
  }
@@ -1866,10 +2056,20 @@ function isRecord(value) {
1866
2056
  function isNodeError(value) {
1867
2057
  return value instanceof Error && typeof value.code === "string";
1868
2058
  }
2059
+ var LOCK_PID_FILE = "pid";
2060
+ function isProcessAlive(pid) {
2061
+ try {
2062
+ process.kill(pid, 0);
2063
+ return true;
2064
+ } catch {
2065
+ return false;
2066
+ }
2067
+ }
1869
2068
  var FileLock = class {
1870
2069
  constructor(filePath, timeoutMs, retryMs) {
1871
2070
  this.acquired = false;
1872
2071
  this.dirPath = `${filePath}.lock`;
2072
+ this.pidFilePath = path3.join(this.dirPath, LOCK_PID_FILE);
1873
2073
  this.timeoutMs = timeoutMs;
1874
2074
  this.retryMs = retryMs;
1875
2075
  }
@@ -1878,12 +2078,18 @@ var FileLock = class {
1878
2078
  while (true) {
1879
2079
  try {
1880
2080
  await fsp.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });
2081
+ await fsp.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
1881
2082
  this.acquired = true;
1882
2083
  return;
1883
2084
  } catch (err) {
1884
2085
  if (!isNodeError(err) || err.code !== "EEXIST") {
1885
2086
  throw err;
1886
2087
  }
2088
+ const stale = await this.isStaleLock();
2089
+ if (stale) {
2090
+ await fsp.rm(this.dirPath, { recursive: true, force: true });
2091
+ continue;
2092
+ }
1887
2093
  if (Date.now() - start >= this.timeoutMs) {
1888
2094
  throw new Error(`IdentityStore lock acquisition timed out for ${this.dirPath}`);
1889
2095
  }
@@ -1891,6 +2097,16 @@ var FileLock = class {
1891
2097
  }
1892
2098
  }
1893
2099
  }
2100
+ async isStaleLock() {
2101
+ 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);
2106
+ } catch {
2107
+ return true;
2108
+ }
2109
+ }
1894
2110
  async release() {
1895
2111
  if (!this.acquired) return;
1896
2112
  this.acquired = false;
@@ -2319,12 +2535,43 @@ var TOOL_CHECK_TIMEOUT_MS = {
2319
2535
  pnpm: 15e3,
2320
2536
  nrm: 15e3
2321
2537
  };
2538
+ var POSIX_LOGIN_SHELL_FALLBACK_TOOLS = /* @__PURE__ */ new Set([
2539
+ "npm",
2540
+ "pnpm",
2541
+ "nrm",
2542
+ "node",
2543
+ "dotnet",
2544
+ "rtk"
2545
+ ]);
2322
2546
  var ERROR_CODE_NOT_FOUND = 127;
2323
2547
  var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
2548
+ var NVM_FALLBACK_DIRS = [
2549
+ "/opt/homebrew/opt/nvm",
2550
+ "/usr/local/opt/nvm",
2551
+ "$HOME/.nvm"
2552
+ ];
2553
+ function resolveUserLoginShell(platform3, envShell) {
2554
+ const shell = envShell?.trim();
2555
+ if (shell && shell.length > 0) {
2556
+ return shell;
2557
+ }
2558
+ return platform3 === "darwin" ? "/bin/zsh" : "/bin/bash";
2559
+ }
2560
+ function buildNvmSourcingSnippet() {
2561
+ const fallbackList = NVM_FALLBACK_DIRS.map((dir) => `"${dir}"`).join(" ");
2562
+ return `for d in $NVM_DIR ${fallbackList}; do [ -n "$d" ] && [ -s "$d/nvm.sh" ] && export NVM_DIR="$d" && . "$d/nvm.sh" && break; done`;
2563
+ }
2564
+ function posixLoginShellArgs(platform3, envShell, command) {
2565
+ return {
2566
+ command: resolveUserLoginShell(platform3, envShell),
2567
+ args: ["-lc", `${buildNvmSourcingSnippet()}; ${command}`]
2568
+ };
2569
+ }
2324
2570
  var EnvironmentInspector = class {
2325
2571
  constructor(options = {}) {
2326
2572
  this.runCommandFn = options.runCommand ?? runCommand;
2327
2573
  this.platform = options.platform ?? process.platform;
2574
+ this.shell = options.shell ?? process.env.SHELL;
2328
2575
  }
2329
2576
  async checkEnvironment() {
2330
2577
  const results = await Promise.all(
@@ -2362,6 +2609,21 @@ var EnvironmentInspector = class {
2362
2609
  timeoutMs: this.getToolTimeout(toolName)
2363
2610
  });
2364
2611
  return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
2612
+ } catch {
2613
+ if (this.shouldUsePosixLoginShellFallback(toolName)) {
2614
+ return this.getToolPathFromLoginShell(toolName);
2615
+ }
2616
+ return void 0;
2617
+ }
2618
+ }
2619
+ async getToolPathFromLoginShell(toolName) {
2620
+ try {
2621
+ const spawnArgs = posixLoginShellArgs(this.platform, this.shell, `command -v ${toolName}`);
2622
+ const result = await this.runCommandFn(spawnArgs.command, {
2623
+ args: spawnArgs.args,
2624
+ timeoutMs: this.getToolTimeout(toolName)
2625
+ });
2626
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
2365
2627
  } catch {
2366
2628
  return void 0;
2367
2629
  }
@@ -2386,12 +2648,31 @@ var EnvironmentInspector = class {
2386
2648
  }
2387
2649
  }
2388
2650
  async getToolVersion(toolName) {
2389
- const invocation = await this.getVersionInvocation(toolName);
2390
- const result = await this.runCommandFn(invocation.command, {
2391
- args: invocation.args,
2392
- timeoutMs: this.getToolTimeout(toolName)
2393
- });
2394
- return this.parseVersion(toolName, result.stdout || result.stderr);
2651
+ try {
2652
+ const invocation = await this.getVersionInvocation(toolName);
2653
+ const result = await this.runCommandFn(invocation.command, {
2654
+ args: invocation.args,
2655
+ timeoutMs: this.getToolTimeout(toolName)
2656
+ });
2657
+ return this.parseVersion(toolName, result.stdout || result.stderr);
2658
+ } catch (error) {
2659
+ if (!this.shouldUsePosixLoginShellFallback(toolName)) {
2660
+ throw error;
2661
+ }
2662
+ const fallbackArgs = posixLoginShellArgs(this.platform, this.shell, `${toolName} --version`);
2663
+ const fallbackResult = await this.runCommandFn(fallbackArgs.command, {
2664
+ args: fallbackArgs.args,
2665
+ timeoutMs: this.getToolTimeout(toolName)
2666
+ });
2667
+ const fallbackOutput = (fallbackResult.stdout || fallbackResult.stderr).trim();
2668
+ if (!fallbackOutput) {
2669
+ throw error;
2670
+ }
2671
+ return this.parseVersion(toolName, fallbackOutput);
2672
+ }
2673
+ }
2674
+ shouldUsePosixLoginShellFallback(toolName) {
2675
+ return this.platform !== "win32" && POSIX_LOGIN_SHELL_FALLBACK_TOOLS.has(toolName);
2395
2676
  }
2396
2677
  async checkNvm() {
2397
2678
  if (this.platform === "win32") {
@@ -2413,11 +2694,9 @@ var EnvironmentInspector = class {
2413
2694
  }
2414
2695
  }
2415
2696
  try {
2416
- const result = await this.runCommandFn("/bin/bash", {
2417
- args: [
2418
- "-lc",
2419
- 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm --version'
2420
- ],
2697
+ const nvmArgs = posixLoginShellArgs(this.platform, this.shell, "nvm --version");
2698
+ const result = await this.runCommandFn(nvmArgs.command, {
2699
+ args: nvmArgs.args,
2421
2700
  timeoutMs: this.getToolTimeout("nvm")
2422
2701
  });
2423
2702
  return {
@@ -2441,7 +2720,7 @@ var EnvironmentInspector = class {
2441
2720
  };
2442
2721
  }
2443
2722
  try {
2444
- const result = await this.runCommandFn("dotnet", {
2723
+ const result = await this.runCommandFn(dotnetPath, {
2445
2724
  args: ["nuget", "list", "source"],
2446
2725
  timeoutMs: this.getToolTimeout("nuget")
2447
2726
  });
@@ -2604,7 +2883,9 @@ var GitClient = class {
2604
2883
  }
2605
2884
  const targetUrl = useProxy ? remoteUrl : originalUrl ?? remoteUrl;
2606
2885
  const proxyUrl = this.rewriteRemoteUrl(repoId, targetUrl, useProxy);
2607
- await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], { cwd: localPath });
2886
+ await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], {
2887
+ cwd: localPath
2888
+ });
2608
2889
  const before = await this.safeRevParse(localPath);
2609
2890
  await this.runOrThrow(["fetch", "origin"], { cwd: localPath });
2610
2891
  const after = await this.safeRevParse(localPath);
@@ -2640,7 +2921,9 @@ var GitClient = class {
2640
2921
  throw new Error(`no 'origin' remote configured at ${localPath}`);
2641
2922
  }
2642
2923
  const proxyUrl = this.rewriteRemoteUrl(repoId, remoteUrl, useProxy);
2643
- await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], { cwd: localPath });
2924
+ await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], {
2925
+ cwd: localPath
2926
+ });
2644
2927
  const result = await this.spawner.spawn(
2645
2928
  ["push", "origin", `refs/heads/${branch}:refs/heads/${branch}`],
2646
2929
  { cwd: localPath }
@@ -2694,7 +2977,9 @@ var GitClient = class {
2694
2977
  return { commitSha: sha };
2695
2978
  }
2696
2979
  async runOrThrow(args, opts) {
2697
- const result = await this.spawner.spawn(args, { cwd: opts.cwd ?? process.cwd() });
2980
+ const result = await this.spawner.spawn(args, {
2981
+ cwd: opts.cwd ?? process.cwd()
2982
+ });
2698
2983
  if (result.code !== 0) {
2699
2984
  throw new GitError(args, result);
2700
2985
  }
@@ -2709,7 +2994,9 @@ var GitClient = class {
2709
2994
  return url.length > 0 ? url : void 0;
2710
2995
  }
2711
2996
  async safeRevParse(localPath) {
2712
- const result = await this.spawner.spawn(["rev-parse", "HEAD"], { cwd: localPath });
2997
+ const result = await this.spawner.spawn(["rev-parse", "HEAD"], {
2998
+ cwd: localPath
2999
+ });
2713
3000
  if (result.code !== 0) return "";
2714
3001
  return result.stdout.trim();
2715
3002
  }
@@ -2756,7 +3043,11 @@ var StubGitSpawner = class {
2756
3043
  this.calls.push({ args, cwd: opts.cwd });
2757
3044
  const next = this.script.shift();
2758
3045
  if (!next) {
2759
- return { stdout: "", stderr: `stub: no scripted response for ${args.join(" ")}`, code: 1 };
3046
+ return {
3047
+ stdout: "",
3048
+ stderr: `stub: no scripted response for ${args.join(" ")}`,
3049
+ code: 1
3050
+ };
2760
3051
  }
2761
3052
  return next;
2762
3053
  }
@@ -2768,6 +3059,10 @@ function toFileUrl(absolutePath) {
2768
3059
  }
2769
3060
  return `file://${normalized}`;
2770
3061
  }
3062
+ var GIT_PROXY_PATH_SUFFIX = "/git-proxy";
3063
+ function buildGitProxyBase(serverBaseUrl) {
3064
+ return `${serverBaseUrl.replace(/\/+$/, "")}${GIT_PROXY_PATH_SUFFIX}`;
3065
+ }
2771
3066
 
2772
3067
  // src/image/imageTools.ts
2773
3068
  import * as fs5 from "fs/promises";
@@ -2977,50 +3272,6 @@ function detectIndent(text) {
2977
3272
  return 2;
2978
3273
  }
2979
3274
 
2980
- // src/logger.ts
2981
- var noopLogger = {
2982
- debug() {
2983
- },
2984
- info() {
2985
- },
2986
- warn() {
2987
- },
2988
- error() {
2989
- }
2990
- };
2991
- function formatArgs(args) {
2992
- return args.map((arg) => {
2993
- if (typeof arg === "string") {
2994
- return arg;
2995
- }
2996
- try {
2997
- return JSON.stringify(arg);
2998
- } catch {
2999
- return String(arg);
3000
- }
3001
- }).join(" ");
3002
- }
3003
- function createConsoleLogger(prefix = "serviceme") {
3004
- return {
3005
- debug(message, ...args) {
3006
- process.stderr.write(`[${prefix}] DEBUG ${message} ${formatArgs(args)}
3007
- `);
3008
- },
3009
- info(message, ...args) {
3010
- process.stderr.write(`[${prefix}] INFO ${message} ${formatArgs(args)}
3011
- `);
3012
- },
3013
- warn(message, ...args) {
3014
- process.stderr.write(`[${prefix}] WARN ${message} ${formatArgs(args)}
3015
- `);
3016
- },
3017
- error(message, ...args) {
3018
- process.stderr.write(`[${prefix}] ERROR ${message} ${formatArgs(args)}
3019
- `);
3020
- }
3021
- };
3022
- }
3023
-
3024
3275
  // src/paths/serverProxyGlobal.ts
3025
3276
  import { randomUUID as randomUUID2 } from "crypto";
3026
3277
  import * as fs6 from "fs/promises";
@@ -3176,7 +3427,7 @@ import {
3176
3427
  // src/utils/fileUtils.ts
3177
3428
  import { constants, createWriteStream } from "fs";
3178
3429
  import { access as access5, copyFile, lstat, mkdir as mkdir5, readdir as readdir3, rename as rename4, rm as rm3 } from "fs/promises";
3179
- import { dirname as dirname7, join as join7 } from "path";
3430
+ import { dirname as dirname7, join as join8 } from "path";
3180
3431
  import yauzl from "yauzl";
3181
3432
  var unzipFile = (zipPath, dest) => {
3182
3433
  return new Promise((resolve3, reject) => {
@@ -3186,11 +3437,11 @@ var unzipFile = (zipPath, dest) => {
3186
3437
  zipfile.readEntry();
3187
3438
  zipfile.on("entry", (entry) => {
3188
3439
  if (/\/$/.test(entry.fileName)) {
3189
- void mkdir5(join7(dest, entry.fileName), { recursive: true }).then(() => {
3440
+ void mkdir5(join8(dest, entry.fileName), { recursive: true }).then(() => {
3190
3441
  zipfile.readEntry();
3191
3442
  }).catch(reject);
3192
3443
  } else {
3193
- const outputPath = join7(dest, entry.fileName);
3444
+ const outputPath = join8(dest, entry.fileName);
3194
3445
  void mkdir5(dirname7(outputPath), { recursive: true }).then(() => {
3195
3446
  zipfile.openReadStream(
3196
3447
  entry,
@@ -3239,7 +3490,7 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
3239
3490
  await mkdir5(destPath, { recursive: true });
3240
3491
  const children = await readdir3(sourcePath);
3241
3492
  for (const child of children) {
3242
- await mergeEntry(join7(sourcePath, child), join7(destPath, child), overwrite);
3493
+ await mergeEntry(join8(sourcePath, child), join8(destPath, child), overwrite);
3243
3494
  }
3244
3495
  await rm3(sourcePath, { recursive: true, force: true });
3245
3496
  return;
@@ -3262,8 +3513,8 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
3262
3513
  await mkdir5(destDir, { recursive: true });
3263
3514
  const files = await readdir3(sourceDir);
3264
3515
  for (const file of files) {
3265
- const sourceFile = join7(sourceDir, file);
3266
- const destFile = join7(destDir, file);
3516
+ const sourceFile = join8(sourceDir, file);
3517
+ const destFile = join8(destDir, file);
3267
3518
  if (!overwrite) {
3268
3519
  try {
3269
3520
  await access5(destFile, constants.F_OK);
@@ -6678,10 +6929,19 @@ function coercePersistedToolbox(parsed) {
6678
6929
  function isNodeError2(value) {
6679
6930
  return value instanceof Error && typeof value.code === "string";
6680
6931
  }
6932
+ function isProcessAlive2(pid) {
6933
+ try {
6934
+ process.kill(pid, 0);
6935
+ return true;
6936
+ } catch {
6937
+ return false;
6938
+ }
6939
+ }
6681
6940
  var ToolboxFileLock = class {
6682
6941
  constructor(filePath, timeoutMs, retryMs) {
6683
6942
  this.acquired = false;
6684
6943
  this.dirPath = `${filePath}.lock`;
6944
+ this.pidFilePath = path25.join(this.dirPath, "pid");
6685
6945
  this.timeoutMs = timeoutMs;
6686
6946
  this.retryMs = retryMs;
6687
6947
  }
@@ -6690,10 +6950,16 @@ var ToolboxFileLock = class {
6690
6950
  while (true) {
6691
6951
  try {
6692
6952
  await fsp2.mkdir(this.dirPath, { mode: LOCK_DIR_MODE2 });
6953
+ await fsp2.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
6693
6954
  this.acquired = true;
6694
6955
  return;
6695
6956
  } catch (err) {
6696
6957
  if (!isNodeError2(err) || err.code !== "EEXIST") throw err;
6958
+ const stale = await this.isStaleLock();
6959
+ if (stale) {
6960
+ await fsp2.rm(this.dirPath, { recursive: true, force: true });
6961
+ continue;
6962
+ }
6697
6963
  if (Date.now() - start >= this.timeoutMs) {
6698
6964
  throw new Error(`ToolboxStore lock acquisition timed out for ${this.dirPath}`);
6699
6965
  }
@@ -6701,6 +6967,16 @@ var ToolboxFileLock = class {
6701
6967
  }
6702
6968
  }
6703
6969
  }
6970
+ async isStaleLock() {
6971
+ try {
6972
+ const pidStr = await fsp2.readFile(this.pidFilePath, "utf8");
6973
+ const pid = Number.parseInt(pidStr.trim(), 10);
6974
+ if (!Number.isFinite(pid) || pid <= 0) return true;
6975
+ return !isProcessAlive2(pid);
6976
+ } catch {
6977
+ return true;
6978
+ }
6979
+ }
6704
6980
  async release() {
6705
6981
  if (!this.acquired) return;
6706
6982
  this.acquired = false;
@@ -6979,6 +7255,7 @@ export {
6979
7255
  EnvironmentInspector,
6980
7256
  FsIdentityFileBackend,
6981
7257
  FsToolboxFileBackend,
7258
+ GIT_PROXY_PATH_SUFFIX,
6982
7259
  GitClient,
6983
7260
  GitError,
6984
7261
  GitHubAuthProvider,
@@ -7045,6 +7322,7 @@ export {
7045
7322
  bootstrapPhase5Placeholders,
7046
7323
  buildDefaultReposFile,
7047
7324
  buildGitHubLocalEmail,
7325
+ buildGitProxyBase,
7048
7326
  buildSignedHeaders,
7049
7327
  copilotDoctor,
7050
7328
  copilotPrompt,