@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.
@@ -27268,6 +27268,145 @@ var init_directory_resolver = __esm({
27268
27268
  });
27269
27269
 
27270
27270
  // src/downloader.js
27271
+ async function acquireFileLock(lockPath, version) {
27272
+ const lockData = {
27273
+ version,
27274
+ pid: process.pid,
27275
+ timestamp: Date.now()
27276
+ };
27277
+ try {
27278
+ await import_fs_extra2.default.writeFile(lockPath, JSON.stringify(lockData), { flag: "wx" });
27279
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27280
+ console.log(`Acquired file lock: ${lockPath}`);
27281
+ }
27282
+ return true;
27283
+ } catch (error2) {
27284
+ if (error2.code === "EEXIST") {
27285
+ try {
27286
+ const existingLock = JSON.parse(await import_fs_extra2.default.readFile(lockPath, "utf-8"));
27287
+ const lockAge = Date.now() - existingLock.timestamp;
27288
+ if (lockAge > LOCK_TIMEOUT_MS) {
27289
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27290
+ console.log(`Removing stale lock file (age: ${Math.round(lockAge / 1e3)}s, pid: ${existingLock.pid})`);
27291
+ }
27292
+ await import_fs_extra2.default.remove(lockPath);
27293
+ return false;
27294
+ }
27295
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27296
+ console.log(`Download in progress by process ${existingLock.pid}, waiting...`);
27297
+ }
27298
+ return false;
27299
+ } catch (readError) {
27300
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27301
+ console.log(`Lock file corrupted, removing: ${readError.message}`);
27302
+ }
27303
+ try {
27304
+ await import_fs_extra2.default.remove(lockPath);
27305
+ } catch {
27306
+ }
27307
+ return false;
27308
+ }
27309
+ }
27310
+ if (error2.code === "EACCES" || error2.code === "EPERM" || error2.code === "EROFS") {
27311
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27312
+ console.log(`Cannot create lock file (${error2.code}): ${lockPath}`);
27313
+ console.log(`File-based locking unavailable, will proceed without cross-process coordination`);
27314
+ }
27315
+ return null;
27316
+ }
27317
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27318
+ console.log(`Unexpected error creating lock file: ${error2.message}`);
27319
+ console.log(`Proceeding without file-based lock`);
27320
+ }
27321
+ return null;
27322
+ }
27323
+ }
27324
+ async function releaseFileLock(lockPath) {
27325
+ try {
27326
+ await import_fs_extra2.default.remove(lockPath);
27327
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27328
+ console.log(`Released file lock: ${lockPath}`);
27329
+ }
27330
+ } catch (error2) {
27331
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27332
+ console.log(`Warning: Could not release lock file: ${error2.message}`);
27333
+ }
27334
+ }
27335
+ }
27336
+ async function waitForFileLock(lockPath, binaryPath) {
27337
+ const startTime = Date.now();
27338
+ while (Date.now() - startTime < MAX_LOCK_WAIT_MS) {
27339
+ if (await import_fs_extra2.default.pathExists(binaryPath)) {
27340
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27341
+ console.log(`Binary now available at ${binaryPath}, download completed by another process`);
27342
+ }
27343
+ return true;
27344
+ }
27345
+ const lockExists = await import_fs_extra2.default.pathExists(lockPath);
27346
+ if (!lockExists) {
27347
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27348
+ console.log(`Lock file removed but binary not found - download may have failed`);
27349
+ }
27350
+ return false;
27351
+ }
27352
+ try {
27353
+ const lockData = JSON.parse(await import_fs_extra2.default.readFile(lockPath, "utf-8"));
27354
+ const lockAge = Date.now() - lockData.timestamp;
27355
+ if (lockAge > LOCK_TIMEOUT_MS) {
27356
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27357
+ console.log(`Lock expired (age: ${Math.round(lockAge / 1e3)}s), will retry download`);
27358
+ }
27359
+ return false;
27360
+ }
27361
+ } catch {
27362
+ }
27363
+ await new Promise((resolve4) => setTimeout(resolve4, LOCK_POLL_INTERVAL_MS));
27364
+ }
27365
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27366
+ console.log(`Timeout waiting for file lock`);
27367
+ }
27368
+ return false;
27369
+ }
27370
+ async function withDownloadLock(version, downloadFn) {
27371
+ const lockKey = version || "latest";
27372
+ if (downloadLocks.has(lockKey)) {
27373
+ const lock = downloadLocks.get(lockKey);
27374
+ const lockAge = Date.now() - lock.timestamp;
27375
+ if (lockAge > LOCK_TIMEOUT_MS) {
27376
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27377
+ console.log(`In-memory lock for version ${lockKey} expired (age: ${Math.round(lockAge / 1e3)}s), removing stale lock`);
27378
+ }
27379
+ downloadLocks.delete(lockKey);
27380
+ } else {
27381
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27382
+ console.log(`Download already in progress in this process for version ${lockKey}, waiting...`);
27383
+ }
27384
+ try {
27385
+ return await lock.promise;
27386
+ } catch (error2) {
27387
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27388
+ console.log(`In-memory locked download failed, will retry: ${error2.message}`);
27389
+ }
27390
+ }
27391
+ }
27392
+ }
27393
+ const downloadPromise = Promise.race([
27394
+ downloadFn(),
27395
+ new Promise(
27396
+ (_2, reject2) => setTimeout(() => reject2(new Error(`Download timeout after ${LOCK_TIMEOUT_MS / 1e3}s`)), LOCK_TIMEOUT_MS)
27397
+ )
27398
+ ]);
27399
+ downloadLocks.set(lockKey, {
27400
+ promise: downloadPromise,
27401
+ timestamp: Date.now()
27402
+ });
27403
+ try {
27404
+ const result = await downloadPromise;
27405
+ return result;
27406
+ } finally {
27407
+ downloadLocks.delete(lockKey);
27408
+ }
27409
+ }
27271
27410
  function detectOsArch() {
27272
27411
  const osType = import_os2.default.platform();
27273
27412
  const archType = import_os2.default.arch();
@@ -27711,16 +27850,64 @@ async function getPackageVersion() {
27711
27850
  return "0.0.0";
27712
27851
  }
27713
27852
  }
27853
+ async function doDownload(version) {
27854
+ const localDir = await getPackageBinDir();
27855
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27856
+ console.log(`Downloading probe binary (version: ${version || "latest"})...`);
27857
+ console.log(`Using binary directory: ${localDir}`);
27858
+ }
27859
+ const isWindows = import_os2.default.platform() === "win32";
27860
+ const binaryName = isWindows ? `${BINARY_NAME}.exe` : `${BINARY_NAME}-binary`;
27861
+ const binaryPath = import_path2.default.join(localDir, binaryName);
27862
+ const { os: osInfo, arch: archInfo } = detectOsArch();
27863
+ let versionToUse = version;
27864
+ let bestAsset;
27865
+ let tagVersion;
27866
+ if (!versionToUse || versionToUse === "0.0.0") {
27867
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27868
+ console.log("No specific version requested, will use the latest release");
27869
+ }
27870
+ const { tag: tag2, assets } = await getLatestRelease(void 0);
27871
+ tagVersion = tag2.startsWith("v") ? tag2.substring(1) : tag2;
27872
+ bestAsset = findBestAsset(assets, osInfo, archInfo);
27873
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27874
+ console.log(`Found release version: ${tagVersion}`);
27875
+ }
27876
+ } else {
27877
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27878
+ console.log(`Direct download for version: ${versionToUse}`);
27879
+ }
27880
+ tagVersion = versionToUse;
27881
+ bestAsset = constructAssetInfo(versionToUse, osInfo, archInfo);
27882
+ }
27883
+ const { assetPath, checksumPath } = await downloadAsset(bestAsset, localDir);
27884
+ const checksumValid = await verifyChecksum(assetPath, checksumPath);
27885
+ if (!checksumValid) {
27886
+ throw new Error("Checksum verification failed");
27887
+ }
27888
+ const extractedBinaryPath = await extractBinary(assetPath, localDir);
27889
+ await saveVersionInfo(tagVersion, localDir);
27890
+ try {
27891
+ await import_fs_extra2.default.remove(assetPath);
27892
+ if (checksumPath) {
27893
+ await import_fs_extra2.default.remove(checksumPath);
27894
+ }
27895
+ } catch (err) {
27896
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27897
+ console.log(`Warning: Could not clean up temporary files: ${err}`);
27898
+ }
27899
+ }
27900
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27901
+ console.log(`Binary successfully installed at ${extractedBinaryPath} (version: ${tagVersion})`);
27902
+ }
27903
+ return extractedBinaryPath;
27904
+ }
27714
27905
  async function downloadProbeBinary(version) {
27715
27906
  try {
27716
27907
  const localDir = await getPackageBinDir();
27717
27908
  if (!version || version === "0.0.0") {
27718
27909
  version = await getPackageVersion();
27719
27910
  }
27720
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27721
- console.log(`Downloading probe binary (version: ${version || "latest"})...`);
27722
- console.log(`Using binary directory: ${localDir}`);
27723
- }
27724
27911
  const isWindows = import_os2.default.platform() === "win32";
27725
27912
  const binaryName = isWindows ? `${BINARY_NAME}.exe` : `${BINARY_NAME}-binary`;
27726
27913
  const binaryPath = import_path2.default.join(localDir, binaryName);
@@ -27736,54 +27923,44 @@ async function downloadProbeBinary(version) {
27736
27923
  console.log(`Existing binary version (${versionInfo?.version || "unknown"}) doesn't match requested version (${version}). Downloading new version...`);
27737
27924
  }
27738
27925
  }
27739
- const { os: osInfo, arch: archInfo } = detectOsArch();
27740
- let versionToUse = version;
27741
- let bestAsset;
27742
- let tagVersion;
27743
- if (!versionToUse || versionToUse === "0.0.0") {
27744
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27745
- console.log("No specific version requested, will use the latest release");
27746
- }
27747
- const { tag: tag2, assets } = await getLatestRelease(void 0);
27748
- tagVersion = tag2.startsWith("v") ? tag2.substring(1) : tag2;
27749
- bestAsset = findBestAsset(assets, osInfo, archInfo);
27750
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27751
- console.log(`Found release version: ${tagVersion}`);
27926
+ const lockPath = import_path2.default.join(localDir, `.probe-download-${version}.lock`);
27927
+ const maxRetries = 3;
27928
+ for (let retry = 0; retry < maxRetries; retry++) {
27929
+ const lockAcquired = await acquireFileLock(lockPath, version);
27930
+ if (lockAcquired === true) {
27931
+ try {
27932
+ const result = await withDownloadLock(version, () => doDownload(version));
27933
+ return result;
27934
+ } finally {
27935
+ await releaseFileLock(lockPath);
27936
+ }
27752
27937
  }
27753
- } else {
27754
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27755
- console.log(`Direct download for version: ${versionToUse}`);
27938
+ if (lockAcquired === null) {
27939
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27940
+ console.log(`File-based locking unavailable, downloading without cross-process coordination`);
27941
+ }
27942
+ return await withDownloadLock(version, () => doDownload(version));
27756
27943
  }
27757
- tagVersion = versionToUse;
27758
- bestAsset = constructAssetInfo(versionToUse, osInfo, archInfo);
27759
- }
27760
- const { assetPath, checksumPath } = await downloadAsset(bestAsset, localDir);
27761
- const checksumValid = await verifyChecksum(assetPath, checksumPath);
27762
- if (!checksumValid) {
27763
- throw new Error("Checksum verification failed");
27764
- }
27765
- const extractedBinaryPath = await extractBinary(assetPath, localDir);
27766
- await saveVersionInfo(tagVersion, localDir);
27767
- try {
27768
- await import_fs_extra2.default.remove(assetPath);
27769
- if (checksumPath) {
27770
- await import_fs_extra2.default.remove(checksumPath);
27944
+ const downloadCompleted = await waitForFileLock(lockPath, binaryPath);
27945
+ if (downloadCompleted) {
27946
+ return binaryPath;
27771
27947
  }
27772
- } catch (err) {
27773
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27774
- console.log(`Warning: Could not clean up temporary files: ${err}`);
27948
+ if (retry < maxRetries - 1) {
27949
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27950
+ console.log(`Retrying download (attempt ${retry + 2}/${maxRetries})...`);
27951
+ }
27775
27952
  }
27776
27953
  }
27777
27954
  if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
27778
- console.log(`Binary successfully installed at ${extractedBinaryPath} (version: ${tagVersion})`);
27955
+ console.log(`All lock attempts exhausted, attempting direct download`);
27779
27956
  }
27780
- return extractedBinaryPath;
27957
+ return await withDownloadLock(version, () => doDownload(version));
27781
27958
  } catch (error2) {
27782
27959
  console.error("Error downloading probe binary:", error2);
27783
27960
  throw error2;
27784
27961
  }
27785
27962
  }
27786
- var import_axios, import_fs_extra2, import_path2, import_crypto, import_util3, import_child_process, import_tar, import_os2, import_url2, exec, REPO_OWNER, REPO_NAME, BINARY_NAME, __filename2, __dirname2;
27963
+ var import_axios, import_fs_extra2, import_path2, import_crypto, import_util3, import_child_process, import_tar, import_os2, import_url2, exec, REPO_OWNER, REPO_NAME, BINARY_NAME, __filename2, __dirname2, downloadLocks, LOCK_TIMEOUT_MS, LOCK_POLL_INTERVAL_MS, MAX_LOCK_WAIT_MS;
27787
27964
  var init_downloader = __esm({
27788
27965
  "src/downloader.js"() {
27789
27966
  "use strict";
@@ -27804,19 +27981,25 @@ var init_downloader = __esm({
27804
27981
  BINARY_NAME = "probe";
27805
27982
  __filename2 = (0, import_url2.fileURLToPath)("file:///");
27806
27983
  __dirname2 = import_path2.default.dirname(__filename2);
27984
+ downloadLocks = /* @__PURE__ */ new Map();
27985
+ LOCK_TIMEOUT_MS = 5 * 60 * 1e3;
27986
+ LOCK_POLL_INTERVAL_MS = 1e3;
27987
+ MAX_LOCK_WAIT_MS = 5 * 60 * 1e3;
27807
27988
  }
27808
27989
  });
27809
27990
 
27810
27991
  // src/utils.js
27811
27992
  async function getBinaryPath(options = {}) {
27812
27993
  const { forceDownload = false, version } = options;
27813
- if (probeBinaryPath && !forceDownload && import_fs_extra3.default.existsSync(probeBinaryPath)) {
27814
- return probeBinaryPath;
27815
- }
27816
27994
  if (process.env.PROBE_PATH && import_fs_extra3.default.existsSync(process.env.PROBE_PATH) && !forceDownload) {
27817
27995
  probeBinaryPath = process.env.PROBE_PATH;
27818
27996
  return probeBinaryPath;
27819
27997
  }
27998
+ if (version && !forceDownload) {
27999
+ console.log(`Specific version ${version} requested. Downloading...`);
28000
+ probeBinaryPath = await downloadProbeBinary(version);
28001
+ return probeBinaryPath;
28002
+ }
27820
28003
  const binDir = await getPackageBinDir();
27821
28004
  const isWindows = process.platform === "win32";
27822
28005
  const binaryName = isWindows ? "probe.exe" : "probe-binary";
package/cjs/index.cjs CHANGED
@@ -206,6 +206,145 @@ var init_directory_resolver = __esm({
206
206
  });
207
207
 
208
208
  // src/downloader.js
209
+ async function acquireFileLock(lockPath, version) {
210
+ const lockData = {
211
+ version,
212
+ pid: process.pid,
213
+ timestamp: Date.now()
214
+ };
215
+ try {
216
+ await import_fs_extra2.default.writeFile(lockPath, JSON.stringify(lockData), { flag: "wx" });
217
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
218
+ console.log(`Acquired file lock: ${lockPath}`);
219
+ }
220
+ return true;
221
+ } catch (error2) {
222
+ if (error2.code === "EEXIST") {
223
+ try {
224
+ const existingLock = JSON.parse(await import_fs_extra2.default.readFile(lockPath, "utf-8"));
225
+ const lockAge = Date.now() - existingLock.timestamp;
226
+ if (lockAge > LOCK_TIMEOUT_MS) {
227
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
228
+ console.log(`Removing stale lock file (age: ${Math.round(lockAge / 1e3)}s, pid: ${existingLock.pid})`);
229
+ }
230
+ await import_fs_extra2.default.remove(lockPath);
231
+ return false;
232
+ }
233
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
234
+ console.log(`Download in progress by process ${existingLock.pid}, waiting...`);
235
+ }
236
+ return false;
237
+ } catch (readError) {
238
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
239
+ console.log(`Lock file corrupted, removing: ${readError.message}`);
240
+ }
241
+ try {
242
+ await import_fs_extra2.default.remove(lockPath);
243
+ } catch {
244
+ }
245
+ return false;
246
+ }
247
+ }
248
+ if (error2.code === "EACCES" || error2.code === "EPERM" || error2.code === "EROFS") {
249
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
250
+ console.log(`Cannot create lock file (${error2.code}): ${lockPath}`);
251
+ console.log(`File-based locking unavailable, will proceed without cross-process coordination`);
252
+ }
253
+ return null;
254
+ }
255
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
256
+ console.log(`Unexpected error creating lock file: ${error2.message}`);
257
+ console.log(`Proceeding without file-based lock`);
258
+ }
259
+ return null;
260
+ }
261
+ }
262
+ async function releaseFileLock(lockPath) {
263
+ try {
264
+ await import_fs_extra2.default.remove(lockPath);
265
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
266
+ console.log(`Released file lock: ${lockPath}`);
267
+ }
268
+ } catch (error2) {
269
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
270
+ console.log(`Warning: Could not release lock file: ${error2.message}`);
271
+ }
272
+ }
273
+ }
274
+ async function waitForFileLock(lockPath, binaryPath) {
275
+ const startTime = Date.now();
276
+ while (Date.now() - startTime < MAX_LOCK_WAIT_MS) {
277
+ if (await import_fs_extra2.default.pathExists(binaryPath)) {
278
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
279
+ console.log(`Binary now available at ${binaryPath}, download completed by another process`);
280
+ }
281
+ return true;
282
+ }
283
+ const lockExists = await import_fs_extra2.default.pathExists(lockPath);
284
+ if (!lockExists) {
285
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
286
+ console.log(`Lock file removed but binary not found - download may have failed`);
287
+ }
288
+ return false;
289
+ }
290
+ try {
291
+ const lockData = JSON.parse(await import_fs_extra2.default.readFile(lockPath, "utf-8"));
292
+ const lockAge = Date.now() - lockData.timestamp;
293
+ if (lockAge > LOCK_TIMEOUT_MS) {
294
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
295
+ console.log(`Lock expired (age: ${Math.round(lockAge / 1e3)}s), will retry download`);
296
+ }
297
+ return false;
298
+ }
299
+ } catch {
300
+ }
301
+ await new Promise((resolve4) => setTimeout(resolve4, LOCK_POLL_INTERVAL_MS));
302
+ }
303
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
304
+ console.log(`Timeout waiting for file lock`);
305
+ }
306
+ return false;
307
+ }
308
+ async function withDownloadLock(version, downloadFn) {
309
+ const lockKey = version || "latest";
310
+ if (downloadLocks.has(lockKey)) {
311
+ const lock = downloadLocks.get(lockKey);
312
+ const lockAge = Date.now() - lock.timestamp;
313
+ if (lockAge > LOCK_TIMEOUT_MS) {
314
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
315
+ console.log(`In-memory lock for version ${lockKey} expired (age: ${Math.round(lockAge / 1e3)}s), removing stale lock`);
316
+ }
317
+ downloadLocks.delete(lockKey);
318
+ } else {
319
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
320
+ console.log(`Download already in progress in this process for version ${lockKey}, waiting...`);
321
+ }
322
+ try {
323
+ return await lock.promise;
324
+ } catch (error2) {
325
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
326
+ console.log(`In-memory locked download failed, will retry: ${error2.message}`);
327
+ }
328
+ }
329
+ }
330
+ }
331
+ const downloadPromise = Promise.race([
332
+ downloadFn(),
333
+ new Promise(
334
+ (_2, reject2) => setTimeout(() => reject2(new Error(`Download timeout after ${LOCK_TIMEOUT_MS / 1e3}s`)), LOCK_TIMEOUT_MS)
335
+ )
336
+ ]);
337
+ downloadLocks.set(lockKey, {
338
+ promise: downloadPromise,
339
+ timestamp: Date.now()
340
+ });
341
+ try {
342
+ const result = await downloadPromise;
343
+ return result;
344
+ } finally {
345
+ downloadLocks.delete(lockKey);
346
+ }
347
+ }
209
348
  function detectOsArch() {
210
349
  const osType = import_os2.default.platform();
211
350
  const archType = import_os2.default.arch();
@@ -649,16 +788,64 @@ async function getPackageVersion() {
649
788
  return "0.0.0";
650
789
  }
651
790
  }
791
+ async function doDownload(version) {
792
+ const localDir = await getPackageBinDir();
793
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
794
+ console.log(`Downloading probe binary (version: ${version || "latest"})...`);
795
+ console.log(`Using binary directory: ${localDir}`);
796
+ }
797
+ const isWindows = import_os2.default.platform() === "win32";
798
+ const binaryName = isWindows ? `${BINARY_NAME}.exe` : `${BINARY_NAME}-binary`;
799
+ const binaryPath = import_path2.default.join(localDir, binaryName);
800
+ const { os: osInfo, arch: archInfo } = detectOsArch();
801
+ let versionToUse = version;
802
+ let bestAsset;
803
+ let tagVersion;
804
+ if (!versionToUse || versionToUse === "0.0.0") {
805
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
806
+ console.log("No specific version requested, will use the latest release");
807
+ }
808
+ const { tag: tag2, assets } = await getLatestRelease(void 0);
809
+ tagVersion = tag2.startsWith("v") ? tag2.substring(1) : tag2;
810
+ bestAsset = findBestAsset(assets, osInfo, archInfo);
811
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
812
+ console.log(`Found release version: ${tagVersion}`);
813
+ }
814
+ } else {
815
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
816
+ console.log(`Direct download for version: ${versionToUse}`);
817
+ }
818
+ tagVersion = versionToUse;
819
+ bestAsset = constructAssetInfo(versionToUse, osInfo, archInfo);
820
+ }
821
+ const { assetPath, checksumPath } = await downloadAsset(bestAsset, localDir);
822
+ const checksumValid = await verifyChecksum(assetPath, checksumPath);
823
+ if (!checksumValid) {
824
+ throw new Error("Checksum verification failed");
825
+ }
826
+ const extractedBinaryPath = await extractBinary(assetPath, localDir);
827
+ await saveVersionInfo(tagVersion, localDir);
828
+ try {
829
+ await import_fs_extra2.default.remove(assetPath);
830
+ if (checksumPath) {
831
+ await import_fs_extra2.default.remove(checksumPath);
832
+ }
833
+ } catch (err) {
834
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
835
+ console.log(`Warning: Could not clean up temporary files: ${err}`);
836
+ }
837
+ }
838
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
839
+ console.log(`Binary successfully installed at ${extractedBinaryPath} (version: ${tagVersion})`);
840
+ }
841
+ return extractedBinaryPath;
842
+ }
652
843
  async function downloadProbeBinary(version) {
653
844
  try {
654
845
  const localDir = await getPackageBinDir();
655
846
  if (!version || version === "0.0.0") {
656
847
  version = await getPackageVersion();
657
848
  }
658
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
659
- console.log(`Downloading probe binary (version: ${version || "latest"})...`);
660
- console.log(`Using binary directory: ${localDir}`);
661
- }
662
849
  const isWindows = import_os2.default.platform() === "win32";
663
850
  const binaryName = isWindows ? `${BINARY_NAME}.exe` : `${BINARY_NAME}-binary`;
664
851
  const binaryPath = import_path2.default.join(localDir, binaryName);
@@ -674,54 +861,44 @@ async function downloadProbeBinary(version) {
674
861
  console.log(`Existing binary version (${versionInfo?.version || "unknown"}) doesn't match requested version (${version}). Downloading new version...`);
675
862
  }
676
863
  }
677
- const { os: osInfo, arch: archInfo } = detectOsArch();
678
- let versionToUse = version;
679
- let bestAsset;
680
- let tagVersion;
681
- if (!versionToUse || versionToUse === "0.0.0") {
682
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
683
- console.log("No specific version requested, will use the latest release");
684
- }
685
- const { tag: tag2, assets } = await getLatestRelease(void 0);
686
- tagVersion = tag2.startsWith("v") ? tag2.substring(1) : tag2;
687
- bestAsset = findBestAsset(assets, osInfo, archInfo);
688
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
689
- console.log(`Found release version: ${tagVersion}`);
864
+ const lockPath = import_path2.default.join(localDir, `.probe-download-${version}.lock`);
865
+ const maxRetries = 3;
866
+ for (let retry = 0; retry < maxRetries; retry++) {
867
+ const lockAcquired = await acquireFileLock(lockPath, version);
868
+ if (lockAcquired === true) {
869
+ try {
870
+ const result = await withDownloadLock(version, () => doDownload(version));
871
+ return result;
872
+ } finally {
873
+ await releaseFileLock(lockPath);
874
+ }
690
875
  }
691
- } else {
692
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
693
- console.log(`Direct download for version: ${versionToUse}`);
876
+ if (lockAcquired === null) {
877
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
878
+ console.log(`File-based locking unavailable, downloading without cross-process coordination`);
879
+ }
880
+ return await withDownloadLock(version, () => doDownload(version));
694
881
  }
695
- tagVersion = versionToUse;
696
- bestAsset = constructAssetInfo(versionToUse, osInfo, archInfo);
697
- }
698
- const { assetPath, checksumPath } = await downloadAsset(bestAsset, localDir);
699
- const checksumValid = await verifyChecksum(assetPath, checksumPath);
700
- if (!checksumValid) {
701
- throw new Error("Checksum verification failed");
702
- }
703
- const extractedBinaryPath = await extractBinary(assetPath, localDir);
704
- await saveVersionInfo(tagVersion, localDir);
705
- try {
706
- await import_fs_extra2.default.remove(assetPath);
707
- if (checksumPath) {
708
- await import_fs_extra2.default.remove(checksumPath);
882
+ const downloadCompleted = await waitForFileLock(lockPath, binaryPath);
883
+ if (downloadCompleted) {
884
+ return binaryPath;
709
885
  }
710
- } catch (err) {
711
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
712
- console.log(`Warning: Could not clean up temporary files: ${err}`);
886
+ if (retry < maxRetries - 1) {
887
+ if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
888
+ console.log(`Retrying download (attempt ${retry + 2}/${maxRetries})...`);
889
+ }
713
890
  }
714
891
  }
715
892
  if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
716
- console.log(`Binary successfully installed at ${extractedBinaryPath} (version: ${tagVersion})`);
893
+ console.log(`All lock attempts exhausted, attempting direct download`);
717
894
  }
718
- return extractedBinaryPath;
895
+ return await withDownloadLock(version, () => doDownload(version));
719
896
  } catch (error2) {
720
897
  console.error("Error downloading probe binary:", error2);
721
898
  throw error2;
722
899
  }
723
900
  }
724
- var import_axios, import_fs_extra2, import_path2, import_crypto, import_util, import_child_process, import_tar, import_os2, import_url2, exec, REPO_OWNER, REPO_NAME, BINARY_NAME, __filename2, __dirname2;
901
+ var import_axios, import_fs_extra2, import_path2, import_crypto, import_util, import_child_process, import_tar, import_os2, import_url2, exec, REPO_OWNER, REPO_NAME, BINARY_NAME, __filename2, __dirname2, downloadLocks, LOCK_TIMEOUT_MS, LOCK_POLL_INTERVAL_MS, MAX_LOCK_WAIT_MS;
725
902
  var init_downloader = __esm({
726
903
  "src/downloader.js"() {
727
904
  "use strict";
@@ -742,19 +919,25 @@ var init_downloader = __esm({
742
919
  BINARY_NAME = "probe";
743
920
  __filename2 = (0, import_url2.fileURLToPath)("file:///");
744
921
  __dirname2 = import_path2.default.dirname(__filename2);
922
+ downloadLocks = /* @__PURE__ */ new Map();
923
+ LOCK_TIMEOUT_MS = 5 * 60 * 1e3;
924
+ LOCK_POLL_INTERVAL_MS = 1e3;
925
+ MAX_LOCK_WAIT_MS = 5 * 60 * 1e3;
745
926
  }
746
927
  });
747
928
 
748
929
  // src/utils.js
749
930
  async function getBinaryPath(options = {}) {
750
931
  const { forceDownload = false, version } = options;
751
- if (probeBinaryPath && !forceDownload && import_fs_extra3.default.existsSync(probeBinaryPath)) {
752
- return probeBinaryPath;
753
- }
754
932
  if (process.env.PROBE_PATH && import_fs_extra3.default.existsSync(process.env.PROBE_PATH) && !forceDownload) {
755
933
  probeBinaryPath = process.env.PROBE_PATH;
756
934
  return probeBinaryPath;
757
935
  }
936
+ if (version && !forceDownload) {
937
+ console.log(`Specific version ${version} requested. Downloading...`);
938
+ probeBinaryPath = await downloadProbeBinary(version);
939
+ return probeBinaryPath;
940
+ }
758
941
  const binDir = await getPackageBinDir();
759
942
  const isWindows = process.platform === "win32";
760
943
  const binaryName = isWindows ? "probe.exe" : "probe-binary";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc142",
3
+ "version": "0.6.0-rc143",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",