@probelabs/probe 0.6.0-rc142 → 0.6.0-rc143

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.
@@ -1876,6 +1876,145 @@ import { exec as execCallback } from "child_process";
1876
1876
  import tar from "tar";
1877
1877
  import os2 from "os";
1878
1878
  import { fileURLToPath as fileURLToPath2 } from "url";
1879
+ async function acquireFileLock(lockPath, version) {
1880
+ const lockData = {
1881
+ version,
1882
+ pid: process.pid,
1883
+ timestamp: Date.now()
1884
+ };
1885
+ try {
1886
+ await fs2.writeFile(lockPath, JSON.stringify(lockData), { flag: "wx" });
1887
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1888
+ console.log(`Acquired file lock: ${lockPath}`);
1889
+ }
1890
+ return true;
1891
+ } catch (error) {
1892
+ if (error.code === "EEXIST") {
1893
+ try {
1894
+ const existingLock = JSON.parse(await fs2.readFile(lockPath, "utf-8"));
1895
+ const lockAge = Date.now() - existingLock.timestamp;
1896
+ if (lockAge > LOCK_TIMEOUT_MS) {
1897
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1898
+ console.log(`Removing stale lock file (age: ${Math.round(lockAge / 1e3)}s, pid: ${existingLock.pid})`);
1899
+ }
1900
+ await fs2.remove(lockPath);
1901
+ return false;
1902
+ }
1903
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1904
+ console.log(`Download in progress by process ${existingLock.pid}, waiting...`);
1905
+ }
1906
+ return false;
1907
+ } catch (readError) {
1908
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1909
+ console.log(`Lock file corrupted, removing: ${readError.message}`);
1910
+ }
1911
+ try {
1912
+ await fs2.remove(lockPath);
1913
+ } catch {
1914
+ }
1915
+ return false;
1916
+ }
1917
+ }
1918
+ if (error.code === "EACCES" || error.code === "EPERM" || error.code === "EROFS") {
1919
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1920
+ console.log(`Cannot create lock file (${error.code}): ${lockPath}`);
1921
+ console.log(`File-based locking unavailable, will proceed without cross-process coordination`);
1922
+ }
1923
+ return null;
1924
+ }
1925
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1926
+ console.log(`Unexpected error creating lock file: ${error.message}`);
1927
+ console.log(`Proceeding without file-based lock`);
1928
+ }
1929
+ return null;
1930
+ }
1931
+ }
1932
+ async function releaseFileLock(lockPath) {
1933
+ try {
1934
+ await fs2.remove(lockPath);
1935
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1936
+ console.log(`Released file lock: ${lockPath}`);
1937
+ }
1938
+ } catch (error) {
1939
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1940
+ console.log(`Warning: Could not release lock file: ${error.message}`);
1941
+ }
1942
+ }
1943
+ }
1944
+ async function waitForFileLock(lockPath, binaryPath) {
1945
+ const startTime = Date.now();
1946
+ while (Date.now() - startTime < MAX_LOCK_WAIT_MS) {
1947
+ if (await fs2.pathExists(binaryPath)) {
1948
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1949
+ console.log(`Binary now available at ${binaryPath}, download completed by another process`);
1950
+ }
1951
+ return true;
1952
+ }
1953
+ const lockExists = await fs2.pathExists(lockPath);
1954
+ if (!lockExists) {
1955
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1956
+ console.log(`Lock file removed but binary not found - download may have failed`);
1957
+ }
1958
+ return false;
1959
+ }
1960
+ try {
1961
+ const lockData = JSON.parse(await fs2.readFile(lockPath, "utf-8"));
1962
+ const lockAge = Date.now() - lockData.timestamp;
1963
+ if (lockAge > LOCK_TIMEOUT_MS) {
1964
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1965
+ console.log(`Lock expired (age: ${Math.round(lockAge / 1e3)}s), will retry download`);
1966
+ }
1967
+ return false;
1968
+ }
1969
+ } catch {
1970
+ }
1971
+ await new Promise((resolve5) => setTimeout(resolve5, LOCK_POLL_INTERVAL_MS));
1972
+ }
1973
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1974
+ console.log(`Timeout waiting for file lock`);
1975
+ }
1976
+ return false;
1977
+ }
1978
+ async function withDownloadLock(version, downloadFn) {
1979
+ const lockKey = version || "latest";
1980
+ if (downloadLocks.has(lockKey)) {
1981
+ const lock = downloadLocks.get(lockKey);
1982
+ const lockAge = Date.now() - lock.timestamp;
1983
+ if (lockAge > LOCK_TIMEOUT_MS) {
1984
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1985
+ console.log(`In-memory lock for version ${lockKey} expired (age: ${Math.round(lockAge / 1e3)}s), removing stale lock`);
1986
+ }
1987
+ downloadLocks.delete(lockKey);
1988
+ } else {
1989
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1990
+ console.log(`Download already in progress in this process for version ${lockKey}, waiting...`);
1991
+ }
1992
+ try {
1993
+ return await lock.promise;
1994
+ } catch (error) {
1995
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
1996
+ console.log(`In-memory locked download failed, will retry: ${error.message}`);
1997
+ }
1998
+ }
1999
+ }
2000
+ }
2001
+ const downloadPromise = Promise.race([
2002
+ downloadFn(),
2003
+ new Promise(
2004
+ (_, reject2) => setTimeout(() => reject2(new Error(`Download timeout after ${LOCK_TIMEOUT_MS / 1e3}s`)), LOCK_TIMEOUT_MS)
2005
+ )
2006
+ ]);
2007
+ downloadLocks.set(lockKey, {
2008
+ promise: downloadPromise,
2009
+ timestamp: Date.now()
2010
+ });
2011
+ try {
2012
+ const result = await downloadPromise;
2013
+ return result;
2014
+ } finally {
2015
+ downloadLocks.delete(lockKey);
2016
+ }
2017
+ }
1879
2018
  function detectOsArch() {
1880
2019
  const osType = os2.platform();
1881
2020
  const archType = os2.arch();
@@ -2319,16 +2458,64 @@ async function getPackageVersion() {
2319
2458
  return "0.0.0";
2320
2459
  }
2321
2460
  }
2461
+ async function doDownload(version) {
2462
+ const localDir = await getPackageBinDir();
2463
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2464
+ console.log(`Downloading probe binary (version: ${version || "latest"})...`);
2465
+ console.log(`Using binary directory: ${localDir}`);
2466
+ }
2467
+ const isWindows = os2.platform() === "win32";
2468
+ const binaryName = isWindows ? `${BINARY_NAME}.exe` : `${BINARY_NAME}-binary`;
2469
+ const binaryPath = path2.join(localDir, binaryName);
2470
+ const { os: osInfo, arch: archInfo } = detectOsArch();
2471
+ let versionToUse = version;
2472
+ let bestAsset;
2473
+ let tagVersion;
2474
+ if (!versionToUse || versionToUse === "0.0.0") {
2475
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2476
+ console.log("No specific version requested, will use the latest release");
2477
+ }
2478
+ const { tag, assets } = await getLatestRelease(void 0);
2479
+ tagVersion = tag.startsWith("v") ? tag.substring(1) : tag;
2480
+ bestAsset = findBestAsset(assets, osInfo, archInfo);
2481
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2482
+ console.log(`Found release version: ${tagVersion}`);
2483
+ }
2484
+ } else {
2485
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2486
+ console.log(`Direct download for version: ${versionToUse}`);
2487
+ }
2488
+ tagVersion = versionToUse;
2489
+ bestAsset = constructAssetInfo(versionToUse, osInfo, archInfo);
2490
+ }
2491
+ const { assetPath, checksumPath } = await downloadAsset(bestAsset, localDir);
2492
+ const checksumValid = await verifyChecksum(assetPath, checksumPath);
2493
+ if (!checksumValid) {
2494
+ throw new Error("Checksum verification failed");
2495
+ }
2496
+ const extractedBinaryPath = await extractBinary(assetPath, localDir);
2497
+ await saveVersionInfo(tagVersion, localDir);
2498
+ try {
2499
+ await fs2.remove(assetPath);
2500
+ if (checksumPath) {
2501
+ await fs2.remove(checksumPath);
2502
+ }
2503
+ } catch (err) {
2504
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2505
+ console.log(`Warning: Could not clean up temporary files: ${err}`);
2506
+ }
2507
+ }
2508
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2509
+ console.log(`Binary successfully installed at ${extractedBinaryPath} (version: ${tagVersion})`);
2510
+ }
2511
+ return extractedBinaryPath;
2512
+ }
2322
2513
  async function downloadProbeBinary(version) {
2323
2514
  try {
2324
2515
  const localDir = await getPackageBinDir();
2325
2516
  if (!version || version === "0.0.0") {
2326
2517
  version = await getPackageVersion();
2327
2518
  }
2328
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2329
- console.log(`Downloading probe binary (version: ${version || "latest"})...`);
2330
- console.log(`Using binary directory: ${localDir}`);
2331
- }
2332
2519
  const isWindows = os2.platform() === "win32";
2333
2520
  const binaryName = isWindows ? `${BINARY_NAME}.exe` : `${BINARY_NAME}-binary`;
2334
2521
  const binaryPath = path2.join(localDir, binaryName);
@@ -2344,54 +2531,44 @@ async function downloadProbeBinary(version) {
2344
2531
  console.log(`Existing binary version (${versionInfo?.version || "unknown"}) doesn't match requested version (${version}). Downloading new version...`);
2345
2532
  }
2346
2533
  }
2347
- const { os: osInfo, arch: archInfo } = detectOsArch();
2348
- let versionToUse = version;
2349
- let bestAsset;
2350
- let tagVersion;
2351
- if (!versionToUse || versionToUse === "0.0.0") {
2352
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2353
- console.log("No specific version requested, will use the latest release");
2354
- }
2355
- const { tag, assets } = await getLatestRelease(void 0);
2356
- tagVersion = tag.startsWith("v") ? tag.substring(1) : tag;
2357
- bestAsset = findBestAsset(assets, osInfo, archInfo);
2358
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2359
- console.log(`Found release version: ${tagVersion}`);
2534
+ const lockPath = path2.join(localDir, `.probe-download-${version}.lock`);
2535
+ const maxRetries = 3;
2536
+ for (let retry = 0; retry < maxRetries; retry++) {
2537
+ const lockAcquired = await acquireFileLock(lockPath, version);
2538
+ if (lockAcquired === true) {
2539
+ try {
2540
+ const result = await withDownloadLock(version, () => doDownload(version));
2541
+ return result;
2542
+ } finally {
2543
+ await releaseFileLock(lockPath);
2544
+ }
2360
2545
  }
2361
- } else {
2362
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2363
- console.log(`Direct download for version: ${versionToUse}`);
2546
+ if (lockAcquired === null) {
2547
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2548
+ console.log(`File-based locking unavailable, downloading without cross-process coordination`);
2549
+ }
2550
+ return await withDownloadLock(version, () => doDownload(version));
2364
2551
  }
2365
- tagVersion = versionToUse;
2366
- bestAsset = constructAssetInfo(versionToUse, osInfo, archInfo);
2367
- }
2368
- const { assetPath, checksumPath } = await downloadAsset(bestAsset, localDir);
2369
- const checksumValid = await verifyChecksum(assetPath, checksumPath);
2370
- if (!checksumValid) {
2371
- throw new Error("Checksum verification failed");
2372
- }
2373
- const extractedBinaryPath = await extractBinary(assetPath, localDir);
2374
- await saveVersionInfo(tagVersion, localDir);
2375
- try {
2376
- await fs2.remove(assetPath);
2377
- if (checksumPath) {
2378
- await fs2.remove(checksumPath);
2552
+ const downloadCompleted = await waitForFileLock(lockPath, binaryPath);
2553
+ if (downloadCompleted) {
2554
+ return binaryPath;
2379
2555
  }
2380
- } catch (err) {
2381
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2382
- console.log(`Warning: Could not clean up temporary files: ${err}`);
2556
+ if (retry < maxRetries - 1) {
2557
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2558
+ console.log(`Retrying download (attempt ${retry + 2}/${maxRetries})...`);
2559
+ }
2383
2560
  }
2384
2561
  }
2385
2562
  if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2386
- console.log(`Binary successfully installed at ${extractedBinaryPath} (version: ${tagVersion})`);
2563
+ console.log(`All lock attempts exhausted, attempting direct download`);
2387
2564
  }
2388
- return extractedBinaryPath;
2565
+ return await withDownloadLock(version, () => doDownload(version));
2389
2566
  } catch (error) {
2390
2567
  console.error("Error downloading probe binary:", error);
2391
2568
  throw error;
2392
2569
  }
2393
2570
  }
2394
- var exec, REPO_OWNER, REPO_NAME, BINARY_NAME, __filename2, __dirname2;
2571
+ var exec, REPO_OWNER, REPO_NAME, BINARY_NAME, __filename2, __dirname2, downloadLocks, LOCK_TIMEOUT_MS, LOCK_POLL_INTERVAL_MS, MAX_LOCK_WAIT_MS;
2395
2572
  var init_downloader = __esm({
2396
2573
  "src/downloader.js"() {
2397
2574
  "use strict";
@@ -2403,6 +2580,10 @@ var init_downloader = __esm({
2403
2580
  BINARY_NAME = "probe";
2404
2581
  __filename2 = fileURLToPath2(import.meta.url);
2405
2582
  __dirname2 = path2.dirname(__filename2);
2583
+ downloadLocks = /* @__PURE__ */ new Map();
2584
+ LOCK_TIMEOUT_MS = 5 * 60 * 1e3;
2585
+ LOCK_POLL_INTERVAL_MS = 1e3;
2586
+ MAX_LOCK_WAIT_MS = 5 * 60 * 1e3;
2406
2587
  }
2407
2588
  });
2408
2589
 
@@ -2412,13 +2593,15 @@ import fs3 from "fs-extra";
2412
2593
  import { fileURLToPath as fileURLToPath3 } from "url";
2413
2594
  async function getBinaryPath(options = {}) {
2414
2595
  const { forceDownload = false, version } = options;
2415
- if (probeBinaryPath && !forceDownload && fs3.existsSync(probeBinaryPath)) {
2416
- return probeBinaryPath;
2417
- }
2418
2596
  if (process.env.PROBE_PATH && fs3.existsSync(process.env.PROBE_PATH) && !forceDownload) {
2419
2597
  probeBinaryPath = process.env.PROBE_PATH;
2420
2598
  return probeBinaryPath;
2421
2599
  }
2600
+ if (version && !forceDownload) {
2601
+ console.log(`Specific version ${version} requested. Downloading...`);
2602
+ probeBinaryPath = await downloadProbeBinary(version);
2603
+ return probeBinaryPath;
2604
+ }
2422
2605
  const binDir = await getPackageBinDir();
2423
2606
  const isWindows = process.platform === "win32";
2424
2607
  const binaryName = isWindows ? "probe.exe" : "probe-binary";