@serviceme/devtools-core 0.3.2 → 0.3.3

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,6 +2535,14 @@ 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";
2324
2548
  var EnvironmentInspector = class {
@@ -2362,6 +2586,23 @@ var EnvironmentInspector = class {
2362
2586
  timeoutMs: this.getToolTimeout(toolName)
2363
2587
  });
2364
2588
  return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
2589
+ } catch {
2590
+ if (this.shouldUsePosixLoginShellFallback(toolName)) {
2591
+ return this.getToolPathFromLoginShell(toolName);
2592
+ }
2593
+ return void 0;
2594
+ }
2595
+ }
2596
+ async getToolPathFromLoginShell(toolName) {
2597
+ 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
+ ],
2603
+ timeoutMs: this.getToolTimeout(toolName)
2604
+ });
2605
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
2365
2606
  } catch {
2366
2607
  return void 0;
2367
2608
  }
@@ -2386,12 +2627,33 @@ var EnvironmentInspector = class {
2386
2627
  }
2387
2628
  }
2388
2629
  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);
2630
+ try {
2631
+ const invocation = await this.getVersionInvocation(toolName);
2632
+ const result = await this.runCommandFn(invocation.command, {
2633
+ args: invocation.args,
2634
+ timeoutMs: this.getToolTimeout(toolName)
2635
+ });
2636
+ return this.parseVersion(toolName, result.stdout || result.stderr);
2637
+ } catch (error) {
2638
+ if (!this.shouldUsePosixLoginShellFallback(toolName)) {
2639
+ throw error;
2640
+ }
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
+ ],
2646
+ timeoutMs: this.getToolTimeout(toolName)
2647
+ });
2648
+ const fallbackOutput = (fallbackResult.stdout || fallbackResult.stderr).trim();
2649
+ if (!fallbackOutput) {
2650
+ throw error;
2651
+ }
2652
+ return this.parseVersion(toolName, fallbackOutput);
2653
+ }
2654
+ }
2655
+ shouldUsePosixLoginShellFallback(toolName) {
2656
+ return this.platform !== "win32" && POSIX_LOGIN_SHELL_FALLBACK_TOOLS.has(toolName);
2395
2657
  }
2396
2658
  async checkNvm() {
2397
2659
  if (this.platform === "win32") {
@@ -2441,7 +2703,7 @@ var EnvironmentInspector = class {
2441
2703
  };
2442
2704
  }
2443
2705
  try {
2444
- const result = await this.runCommandFn("dotnet", {
2706
+ const result = await this.runCommandFn(dotnetPath, {
2445
2707
  args: ["nuget", "list", "source"],
2446
2708
  timeoutMs: this.getToolTimeout("nuget")
2447
2709
  });
@@ -2977,50 +3239,6 @@ function detectIndent(text) {
2977
3239
  return 2;
2978
3240
  }
2979
3241
 
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
3242
  // src/paths/serverProxyGlobal.ts
3025
3243
  import { randomUUID as randomUUID2 } from "crypto";
3026
3244
  import * as fs6 from "fs/promises";
@@ -3176,7 +3394,7 @@ import {
3176
3394
  // src/utils/fileUtils.ts
3177
3395
  import { constants, createWriteStream } from "fs";
3178
3396
  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";
3397
+ import { dirname as dirname7, join as join8 } from "path";
3180
3398
  import yauzl from "yauzl";
3181
3399
  var unzipFile = (zipPath, dest) => {
3182
3400
  return new Promise((resolve3, reject) => {
@@ -3186,11 +3404,11 @@ var unzipFile = (zipPath, dest) => {
3186
3404
  zipfile.readEntry();
3187
3405
  zipfile.on("entry", (entry) => {
3188
3406
  if (/\/$/.test(entry.fileName)) {
3189
- void mkdir5(join7(dest, entry.fileName), { recursive: true }).then(() => {
3407
+ void mkdir5(join8(dest, entry.fileName), { recursive: true }).then(() => {
3190
3408
  zipfile.readEntry();
3191
3409
  }).catch(reject);
3192
3410
  } else {
3193
- const outputPath = join7(dest, entry.fileName);
3411
+ const outputPath = join8(dest, entry.fileName);
3194
3412
  void mkdir5(dirname7(outputPath), { recursive: true }).then(() => {
3195
3413
  zipfile.openReadStream(
3196
3414
  entry,
@@ -3239,7 +3457,7 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
3239
3457
  await mkdir5(destPath, { recursive: true });
3240
3458
  const children = await readdir3(sourcePath);
3241
3459
  for (const child of children) {
3242
- await mergeEntry(join7(sourcePath, child), join7(destPath, child), overwrite);
3460
+ await mergeEntry(join8(sourcePath, child), join8(destPath, child), overwrite);
3243
3461
  }
3244
3462
  await rm3(sourcePath, { recursive: true, force: true });
3245
3463
  return;
@@ -3262,8 +3480,8 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
3262
3480
  await mkdir5(destDir, { recursive: true });
3263
3481
  const files = await readdir3(sourceDir);
3264
3482
  for (const file of files) {
3265
- const sourceFile = join7(sourceDir, file);
3266
- const destFile = join7(destDir, file);
3483
+ const sourceFile = join8(sourceDir, file);
3484
+ const destFile = join8(destDir, file);
3267
3485
  if (!overwrite) {
3268
3486
  try {
3269
3487
  await access5(destFile, constants.F_OK);
@@ -6678,10 +6896,19 @@ function coercePersistedToolbox(parsed) {
6678
6896
  function isNodeError2(value) {
6679
6897
  return value instanceof Error && typeof value.code === "string";
6680
6898
  }
6899
+ function isProcessAlive2(pid) {
6900
+ try {
6901
+ process.kill(pid, 0);
6902
+ return true;
6903
+ } catch {
6904
+ return false;
6905
+ }
6906
+ }
6681
6907
  var ToolboxFileLock = class {
6682
6908
  constructor(filePath, timeoutMs, retryMs) {
6683
6909
  this.acquired = false;
6684
6910
  this.dirPath = `${filePath}.lock`;
6911
+ this.pidFilePath = path25.join(this.dirPath, "pid");
6685
6912
  this.timeoutMs = timeoutMs;
6686
6913
  this.retryMs = retryMs;
6687
6914
  }
@@ -6690,10 +6917,16 @@ var ToolboxFileLock = class {
6690
6917
  while (true) {
6691
6918
  try {
6692
6919
  await fsp2.mkdir(this.dirPath, { mode: LOCK_DIR_MODE2 });
6920
+ await fsp2.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
6693
6921
  this.acquired = true;
6694
6922
  return;
6695
6923
  } catch (err) {
6696
6924
  if (!isNodeError2(err) || err.code !== "EEXIST") throw err;
6925
+ const stale = await this.isStaleLock();
6926
+ if (stale) {
6927
+ await fsp2.rm(this.dirPath, { recursive: true, force: true });
6928
+ continue;
6929
+ }
6697
6930
  if (Date.now() - start >= this.timeoutMs) {
6698
6931
  throw new Error(`ToolboxStore lock acquisition timed out for ${this.dirPath}`);
6699
6932
  }
@@ -6701,6 +6934,16 @@ var ToolboxFileLock = class {
6701
6934
  }
6702
6935
  }
6703
6936
  }
6937
+ async isStaleLock() {
6938
+ 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);
6943
+ } catch {
6944
+ return true;
6945
+ }
6946
+ }
6704
6947
  async release() {
6705
6948
  if (!this.acquired) return;
6706
6949
  this.acquired = false;