@probelabs/probe 0.6.0-rc57 → 0.6.0-rc58

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/build/cli.js ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI wrapper for the probe binary
5
+ *
6
+ * This script ensures the probe binary is downloaded and then executes it with the provided arguments.
7
+ * It's designed to be as lightweight as possible, essentially just passing through to the actual binary.
8
+ */
9
+
10
+ import { spawn } from 'child_process';
11
+ import { getBinaryPath } from './utils.js';
12
+
13
+ /**
14
+ * Main function
15
+ */
16
+ async function main() {
17
+ try {
18
+ // Get the path to the probe binary (this will download it if needed)
19
+ const binaryPath = await getBinaryPath();
20
+
21
+ // Get the arguments passed to the CLI
22
+ const args = process.argv.slice(2);
23
+
24
+ // Spawn the probe binary with the provided arguments
25
+ const probeProcess = spawn(binaryPath, args, {
26
+ stdio: 'inherit' // Pipe stdin/stdout/stderr to the parent process
27
+ });
28
+
29
+ // Handle process exit
30
+ probeProcess.on('close', (code) => {
31
+ process.exit(code);
32
+ });
33
+
34
+ // Handle process errors
35
+ probeProcess.on('error', (error) => {
36
+ console.error(`Error executing probe binary: ${error.message}`);
37
+ process.exit(1);
38
+ });
39
+ } catch (error) {
40
+ console.error(`Error: ${error.message}`);
41
+ process.exit(1);
42
+ }
43
+ }
44
+
45
+ // Execute the main function
46
+ main().catch(error => {
47
+ console.error(`Unexpected error: ${error.message}`);
48
+ process.exit(1);
49
+ });
@@ -0,0 +1,661 @@
1
+ /**
2
+ * Binary downloader for the probe package
3
+ * @module downloader
4
+ */
5
+
6
+ import axios from 'axios';
7
+ import fs from 'fs-extra';
8
+ import path from 'path';
9
+ import { createHash } from 'crypto';
10
+ import { promisify } from 'util';
11
+ import { exec as execCallback } from 'child_process';
12
+ import tar from 'tar';
13
+ import os from 'os';
14
+ import { fileURLToPath } from 'url';
15
+ import { ensureBinDirectory } from './utils.js';
16
+
17
+ const exec = promisify(execCallback);
18
+
19
+ // GitHub repository information
20
+ const REPO_OWNER = "buger";
21
+ const REPO_NAME = "probe";
22
+ const BINARY_NAME = "probe";
23
+
24
+ // Get the directory of the current module
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = path.dirname(__filename);
27
+
28
+ // Local storage directory for downloaded binaries
29
+ const LOCAL_DIR = path.resolve(__dirname, '..', 'bin');
30
+
31
+ // Version info file path
32
+ const VERSION_INFO_PATH = path.join(LOCAL_DIR, 'version-info.json');
33
+
34
+ /**
35
+ * Detects the current OS and architecture
36
+ * @returns {Object} Object containing OS and architecture information
37
+ */
38
+ function detectOsArch() {
39
+ const osType = os.platform();
40
+ const archType = os.arch();
41
+
42
+ let osInfo;
43
+ let archInfo;
44
+
45
+ // Detect OS
46
+ switch (osType) {
47
+ case 'linux':
48
+ osInfo = {
49
+ type: 'linux',
50
+ keywords: ['linux', 'Linux', 'gnu']
51
+ };
52
+ break;
53
+ case 'darwin':
54
+ osInfo = {
55
+ type: 'darwin',
56
+ keywords: ['darwin', 'Darwin', 'mac', 'Mac', 'apple', 'Apple', 'osx', 'OSX']
57
+ };
58
+ break;
59
+ case 'win32':
60
+ osInfo = {
61
+ type: 'windows',
62
+ keywords: ['windows', 'Windows', 'msvc', 'pc-windows']
63
+ };
64
+ break;
65
+ default:
66
+ throw new Error(`Unsupported operating system: ${osType}`);
67
+ }
68
+
69
+ // Detect architecture
70
+ switch (archType) {
71
+ case 'x64':
72
+ archInfo = {
73
+ type: 'x86_64',
74
+ keywords: ['x86_64', 'amd64', 'x64', '64bit', '64-bit']
75
+ };
76
+ break;
77
+ case 'arm64':
78
+ archInfo = {
79
+ type: 'aarch64',
80
+ keywords: ['arm64', 'aarch64', 'arm', 'ARM']
81
+ };
82
+ break;
83
+ default:
84
+ throw new Error(`Unsupported architecture: ${archType}`);
85
+ }
86
+
87
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
88
+ console.log(`Detected OS: ${osInfo.type}, Architecture: ${archInfo.type}`);
89
+ }
90
+ return { os: osInfo, arch: archInfo };
91
+ }
92
+
93
+ /**
94
+ * Gets the latest release information from GitHub
95
+ * @param {string} [version] - Specific version to get
96
+ * @returns {Promise<Object>} Release information
97
+ */
98
+ async function getLatestRelease(version) {
99
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
100
+ console.log('Fetching release information...');
101
+ }
102
+
103
+ try {
104
+ let releaseUrl;
105
+ if (version) {
106
+ // Always use the specified version
107
+ releaseUrl = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/v${version}`;
108
+ } else {
109
+ // Use the latest release only if no version is specified
110
+ releaseUrl = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;
111
+ }
112
+
113
+ const response = await axios.get(releaseUrl);
114
+
115
+ if (response.status !== 200) {
116
+ throw new Error(`Failed to fetch release information: ${response.statusText}`);
117
+ }
118
+
119
+ const tag = response.data.tag_name;
120
+ const assets = response.data.assets.map(asset => ({
121
+ name: asset.name,
122
+ url: asset.browser_download_url
123
+ }));
124
+
125
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
126
+ console.log(`Found release: ${tag} with ${assets.length} assets`);
127
+ }
128
+ return { tag, assets };
129
+ } catch (error) {
130
+ if (axios.isAxiosError(error) && error.response?.status === 404) {
131
+ // If the specific version is not found, try to get all releases
132
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
133
+ console.log(`Release v${version} not found, trying to fetch all releases...`);
134
+ }
135
+
136
+ const response = await axios.get(`https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases`);
137
+
138
+ if (response.data.length === 0) {
139
+ throw new Error('No releases found');
140
+ }
141
+
142
+ // Try to find a release that matches the version prefix
143
+ let bestRelease = response.data[0]; // Default to first release
144
+
145
+ if (version && version !== '0.0.0') {
146
+ // Try to find a release that starts with the same version prefix
147
+ const versionParts = version.split('.');
148
+ const versionPrefix = versionParts.slice(0, 2).join('.'); // e.g., "0.2" from "0.2.2-rc7"
149
+
150
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
151
+ console.log(`Looking for releases matching prefix: ${versionPrefix}`);
152
+ }
153
+
154
+ for (const release of response.data) {
155
+ const releaseTag = release.tag_name.startsWith('v') ?
156
+ release.tag_name.substring(1) : release.tag_name;
157
+
158
+ if (releaseTag.startsWith(versionPrefix)) {
159
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
160
+ console.log(`Found matching release: ${release.tag_name}`);
161
+ }
162
+ bestRelease = release;
163
+ break;
164
+ }
165
+ }
166
+ }
167
+
168
+ const tag = bestRelease.tag_name;
169
+ const assets = bestRelease.assets.map(asset => ({
170
+ name: asset.name,
171
+ url: asset.browser_download_url
172
+ }));
173
+
174
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
175
+ console.log(`Using release: ${tag} with ${assets.length} assets`);
176
+ }
177
+ return { tag, assets };
178
+ }
179
+
180
+ throw error;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Finds the best matching asset for the current OS and architecture
186
+ * @param {Array} assets - List of assets
187
+ * @param {Object} osInfo - OS information
188
+ * @param {Object} archInfo - Architecture information
189
+ * @returns {Object} Best matching asset
190
+ */
191
+ function findBestAsset(assets, osInfo, archInfo) {
192
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
193
+ console.log(`Finding appropriate binary for ${osInfo.type} ${archInfo.type}...`);
194
+ }
195
+
196
+ let bestAsset = null;
197
+ let bestScore = 0;
198
+
199
+ for (const asset of assets) {
200
+ // Skip checksum files
201
+ if (asset.name.endsWith('.sha256') || asset.name.endsWith('.md5') || asset.name.endsWith('.asc')) {
202
+ continue;
203
+ }
204
+
205
+ if (osInfo.type === 'windows' && asset.name.match(/darwin|linux/)) {
206
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
207
+ console.log(`Skipping non-Windows binary: ${asset.name}`);
208
+ }
209
+ continue;
210
+ } else if (osInfo.type === 'darwin' && asset.name.match(/windows|msvc|linux/)) {
211
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
212
+ console.log(`Skipping non-macOS binary: ${asset.name}`);
213
+ }
214
+ continue;
215
+ } else if (osInfo.type === 'linux' && asset.name.match(/darwin|windows|msvc/)) {
216
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
217
+ console.log(`Skipping non-Linux binary: ${asset.name}`);
218
+ }
219
+ continue;
220
+ }
221
+
222
+ let score = 0;
223
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
224
+ console.log(`Evaluating asset: ${asset.name}`);
225
+ }
226
+
227
+ // Check for OS match - give higher priority to exact OS matches
228
+ let osMatched = false;
229
+ for (const keyword of osInfo.keywords) {
230
+ if (asset.name.includes(keyword)) {
231
+ score += 10;
232
+ osMatched = true;
233
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
234
+ console.log(` OS match found (${keyword}): +10, score = ${score}`);
235
+ }
236
+ break;
237
+ }
238
+ }
239
+
240
+ // Check for architecture match
241
+ for (const keyword of archInfo.keywords) {
242
+ if (asset.name.includes(keyword)) {
243
+ score += 5;
244
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
245
+ console.log(` Arch match found (${keyword}): +5, score = ${score}`);
246
+ }
247
+ break;
248
+ }
249
+ }
250
+
251
+ // Prefer exact matches for binary name
252
+ if (asset.name.startsWith(`${BINARY_NAME}-`)) {
253
+ score += 3;
254
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
255
+ console.log(` Binary name match: +3, score = ${score}`);
256
+ }
257
+ }
258
+
259
+ if (osMatched && score >= 15) {
260
+ score += 5;
261
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
262
+ console.log(` OS+Arch bonus: +5, score = ${score}`);
263
+ }
264
+ }
265
+
266
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
267
+ console.log(` Final score for ${asset.name}: ${score}`);
268
+ }
269
+
270
+ // If we have a perfect match, use it immediately
271
+ if (score === 23) {
272
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
273
+ console.log(`Found perfect match: ${asset.name}`);
274
+ }
275
+ return asset;
276
+ }
277
+
278
+ // Otherwise, keep track of the best match so far
279
+ if (score > bestScore) {
280
+ bestScore = score;
281
+ bestAsset = asset;
282
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
283
+ console.log(` New best asset: ${asset.name} (score: ${score})`);
284
+ }
285
+ }
286
+ }
287
+
288
+ if (!bestAsset) {
289
+ throw new Error(`Could not find a suitable binary for ${osInfo.type} ${archInfo.type}`);
290
+ }
291
+
292
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
293
+ console.log(`Selected asset: ${bestAsset.name} (score: ${bestScore})`);
294
+ }
295
+ return bestAsset;
296
+ }
297
+
298
+ /**
299
+ * Downloads the asset and its checksum
300
+ * @param {Object} asset - Asset to download
301
+ * @param {string} outputDir - Directory to save to
302
+ * @returns {Promise<Object>} Paths to the asset and checksum
303
+ */
304
+ async function downloadAsset(asset, outputDir) {
305
+ await fs.ensureDir(outputDir);
306
+
307
+ const assetPath = path.join(outputDir, asset.name);
308
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
309
+ console.log(`Downloading ${asset.name}...`);
310
+ }
311
+
312
+ // Download the asset
313
+ const assetResponse = await axios.get(asset.url, { responseType: 'arraybuffer' });
314
+ await fs.writeFile(assetPath, Buffer.from(assetResponse.data));
315
+
316
+ // Try to download the checksum
317
+ const checksumUrl = `${asset.url}.sha256`;
318
+ let checksumPath = null;
319
+
320
+ try {
321
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
322
+ console.log(`Downloading checksum...`);
323
+ }
324
+ const checksumResponse = await axios.get(checksumUrl);
325
+ checksumPath = path.join(outputDir, `${asset.name}.sha256`);
326
+ await fs.writeFile(checksumPath, checksumResponse.data);
327
+ } catch (error) {
328
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
329
+ console.log('No checksum file found, skipping verification');
330
+ }
331
+ }
332
+
333
+ return { assetPath, checksumPath };
334
+ }
335
+
336
+ /**
337
+ * Verifies the checksum of the downloaded asset
338
+ * @param {string} assetPath - Path to the asset
339
+ * @param {string|null} checksumPath - Path to the checksum file
340
+ * @returns {Promise<boolean>} Whether verification succeeded
341
+ */
342
+ async function verifyChecksum(assetPath, checksumPath) {
343
+ if (!checksumPath) {
344
+ return true;
345
+ }
346
+
347
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
348
+ console.log(`Verifying checksum...`);
349
+ }
350
+
351
+ // Read the expected checksum
352
+ const checksumContent = await fs.readFile(checksumPath, 'utf-8');
353
+ const expectedChecksum = checksumContent.trim().split(' ')[0];
354
+
355
+ // Calculate the actual checksum
356
+ const fileBuffer = await fs.readFile(assetPath);
357
+ const actualChecksum = createHash('sha256').update(fileBuffer).digest('hex');
358
+
359
+ if (expectedChecksum !== actualChecksum) {
360
+ console.error(`Checksum verification failed!`);
361
+ console.error(`Expected: ${expectedChecksum}`);
362
+ console.error(`Actual: ${actualChecksum}`);
363
+ return false;
364
+ }
365
+
366
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
367
+ console.log(`Checksum verified successfully`);
368
+ }
369
+ return true;
370
+ }
371
+
372
+ /**
373
+ * Extracts and installs the binary
374
+ * @param {string} assetPath - Path to the asset
375
+ * @param {string} outputDir - Directory to extract to
376
+ * @returns {Promise<string>} Path to the extracted binary
377
+ */
378
+ async function extractBinary(assetPath, outputDir) {
379
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
380
+ console.log(`Extracting ${path.basename(assetPath)}...`);
381
+ }
382
+
383
+ const assetName = path.basename(assetPath);
384
+ const isWindows = os.platform() === 'win32';
385
+ // Use the correct binary name: probe.exe for Windows, probe-binary for Unix
386
+ const binaryName = isWindows ? `${BINARY_NAME}.exe` : `${BINARY_NAME}-binary`;
387
+ const binaryPath = path.join(outputDir, binaryName);
388
+
389
+ try {
390
+ // Create a temporary extraction directory
391
+ const extractDir = path.join(outputDir, 'temp_extract');
392
+ await fs.ensureDir(extractDir);
393
+
394
+ // Determine file type and extract accordingly
395
+ if (assetName.endsWith('.tar.gz') || assetName.endsWith('.tgz')) {
396
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
397
+ console.log(`Extracting tar.gz to ${extractDir}...`);
398
+ }
399
+ await tar.extract({
400
+ file: assetPath,
401
+ cwd: extractDir
402
+ });
403
+ } else if (assetName.endsWith('.zip')) {
404
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
405
+ console.log(`Extracting zip to ${extractDir}...`);
406
+ }
407
+ await exec(`unzip -q "${assetPath}" -d "${extractDir}"`);
408
+ } else {
409
+ // Assume it's a direct binary
410
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
411
+ console.log(`Copying binary directly to ${binaryPath}`);
412
+ }
413
+ await fs.copyFile(assetPath, binaryPath);
414
+
415
+ // Make the binary executable
416
+ if (!isWindows) {
417
+ await fs.chmod(binaryPath, 0o755);
418
+ }
419
+
420
+ // Clean up the extraction directory
421
+ await fs.remove(extractDir);
422
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
423
+ console.log(`Binary installed to ${binaryPath}`);
424
+ }
425
+ return binaryPath;
426
+ }
427
+
428
+ // Find the binary in the extracted files
429
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
430
+ console.log(`Searching for binary in extracted files...`);
431
+ }
432
+ const findBinary = async (dir) => {
433
+ const entries = await fs.readdir(dir, { withFileTypes: true });
434
+
435
+ for (const entry of entries) {
436
+ const fullPath = path.join(dir, entry.name);
437
+
438
+ if (entry.isDirectory()) {
439
+ const result = await findBinary(fullPath);
440
+ if (result) return result;
441
+ } else if (entry.isFile()) {
442
+ // Check if this is the binary we're looking for
443
+ if (entry.name === binaryName ||
444
+ entry.name === BINARY_NAME ||
445
+ (isWindows && entry.name.endsWith('.exe'))) {
446
+ return fullPath;
447
+ }
448
+ }
449
+ }
450
+
451
+ return null;
452
+ };
453
+
454
+ const binaryFilePath = await findBinary(extractDir);
455
+
456
+ if (!binaryFilePath) {
457
+ // List all extracted files for debugging
458
+ const allFiles = await fs.readdir(extractDir, { recursive: true });
459
+ console.error(`Binary not found in extracted files. Found: ${allFiles.join(', ')}`);
460
+ throw new Error(`Binary not found in the archive.`);
461
+ }
462
+
463
+ // Copy the binary directly to the final location
464
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
465
+ console.log(`Found binary at ${binaryFilePath}`);
466
+ console.log(`Copying binary to ${binaryPath}`);
467
+ }
468
+ await fs.copyFile(binaryFilePath, binaryPath);
469
+
470
+ // Make the binary executable
471
+ if (!isWindows) {
472
+ await fs.chmod(binaryPath, 0o755);
473
+ }
474
+
475
+ // Clean up
476
+ await fs.remove(extractDir);
477
+
478
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
479
+ console.log(`Binary successfully installed to ${binaryPath}`);
480
+ }
481
+ return binaryPath;
482
+ } catch (error) {
483
+ console.error(`Error extracting binary: ${error instanceof Error ? error.message : String(error)}`);
484
+ throw error;
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Gets version info from the version file
490
+ * @returns {Promise<Object|null>} Version information
491
+ */
492
+ async function getVersionInfo() {
493
+ try {
494
+ if (await fs.pathExists(VERSION_INFO_PATH)) {
495
+ const content = await fs.readFile(VERSION_INFO_PATH, 'utf-8');
496
+ return JSON.parse(content);
497
+ }
498
+ return null;
499
+ } catch (error) {
500
+ console.warn(`Warning: Could not read version info: ${error}`);
501
+ return null;
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Saves version info to the version file
507
+ * @param {string} version - Version to save
508
+ * @returns {Promise<void>}
509
+ */
510
+ async function saveVersionInfo(version) {
511
+ const versionInfo = {
512
+ version,
513
+ lastUpdated: new Date().toISOString()
514
+ };
515
+
516
+ await fs.writeFile(VERSION_INFO_PATH, JSON.stringify(versionInfo, null, 2));
517
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
518
+ console.log(`Version info saved: ${version}`);
519
+ }
520
+ }
521
+
522
+ /**
523
+ * Gets the package version from package.json
524
+ * @returns {Promise<string>} Package version
525
+ */
526
+ async function getPackageVersion() {
527
+ try {
528
+ // Try multiple possible locations for package.json
529
+ const possiblePaths = [
530
+ path.resolve(__dirname, '..', 'package.json'), // When installed from npm: src/../package.json
531
+ path.resolve(__dirname, '..', '..', 'package.json') // In development: src/../../package.json
532
+ ];
533
+
534
+ for (const packageJsonPath of possiblePaths) {
535
+ try {
536
+ if (fs.existsSync(packageJsonPath)) {
537
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
538
+ console.log(`Found package.json at: ${packageJsonPath}`);
539
+ }
540
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
541
+ if (packageJson.version) {
542
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
543
+ console.log(`Using version from package.json: ${packageJson.version}`);
544
+ }
545
+ return packageJson.version;
546
+ }
547
+ }
548
+ } catch (err) {
549
+ console.error(`Error reading package.json at ${packageJsonPath}:`, err);
550
+ }
551
+ }
552
+
553
+ // If we can't find the version in package.json, return a default version
554
+ return '0.0.0';
555
+ } catch (error) {
556
+ console.error('Error getting package version:', error);
557
+ return '0.0.0';
558
+ }
559
+ }
560
+
561
+ /**
562
+ * Downloads the probe binary
563
+ * @param {string} [version] - Specific version to download
564
+ * @returns {Promise<string>} Path to the downloaded binary
565
+ */
566
+ export async function downloadProbeBinary(version) {
567
+ try {
568
+ // Create the bin directory if it doesn't exist
569
+ await ensureBinDirectory();
570
+
571
+ // If no version is specified, use the package version
572
+ if (!version || version === '0.0.0') {
573
+ version = await getPackageVersion();
574
+ }
575
+
576
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
577
+ console.log(`Downloading probe binary (version: ${version || 'latest'})...`);
578
+ }
579
+
580
+ const isWindows = os.platform() === 'win32';
581
+ // Use the correct binary name: probe.exe for Windows, probe-binary for Unix
582
+ const binaryName = isWindows ? `${BINARY_NAME}.exe` : `${BINARY_NAME}-binary`;
583
+ const binaryPath = path.join(LOCAL_DIR, binaryName);
584
+
585
+ // Check if the binary already exists and version matches
586
+ if (await fs.pathExists(binaryPath)) {
587
+ const versionInfo = await getVersionInfo();
588
+
589
+ // If versions match, use existing binary
590
+ if (versionInfo && versionInfo.version === version) {
591
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
592
+ console.log(`Using existing binary at ${binaryPath} (version: ${versionInfo.version})`);
593
+ }
594
+ return binaryPath;
595
+ }
596
+
597
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
598
+ console.log(`Existing binary version (${versionInfo?.version || 'unknown'}) doesn't match requested version (${version}). Downloading new version...`);
599
+ }
600
+ }
601
+
602
+ // Get OS and architecture information
603
+ const { os: osInfo, arch: archInfo } = detectOsArch();
604
+
605
+ // Determine which version to download
606
+ let versionToUse = version;
607
+ if (!versionToUse || versionToUse === '0.0.0') {
608
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
609
+ console.log('No specific version requested, will use the latest release');
610
+ }
611
+ versionToUse = undefined;
612
+ } else {
613
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
614
+ console.log(`Looking for release with version: ${versionToUse}`);
615
+ }
616
+ }
617
+
618
+ // Get release information
619
+ const { tag, assets } = await getLatestRelease(versionToUse);
620
+ const tagVersion = tag.startsWith('v') ? tag.substring(1) : tag;
621
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
622
+ console.log(`Found release version: ${tagVersion}`);
623
+ }
624
+
625
+ // Find and download the appropriate asset
626
+ const bestAsset = findBestAsset(assets, osInfo, archInfo);
627
+ const { assetPath, checksumPath } = await downloadAsset(bestAsset, LOCAL_DIR);
628
+
629
+ // Verify checksum if available
630
+ const checksumValid = await verifyChecksum(assetPath, checksumPath);
631
+ if (!checksumValid) {
632
+ throw new Error('Checksum verification failed');
633
+ }
634
+
635
+ // Extract the binary
636
+ const extractedBinaryPath = await extractBinary(assetPath, LOCAL_DIR);
637
+
638
+ // Save the version information
639
+ await saveVersionInfo(tagVersion);
640
+
641
+ // Clean up the downloaded archive
642
+ try {
643
+ await fs.remove(assetPath);
644
+ if (checksumPath) {
645
+ await fs.remove(checksumPath);
646
+ }
647
+ } catch (err) {
648
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
649
+ console.log(`Warning: Could not clean up temporary files: ${err}`);
650
+ }
651
+ }
652
+
653
+ if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') {
654
+ console.log(`Binary successfully installed at ${extractedBinaryPath} (version: ${tagVersion})`);
655
+ }
656
+ return extractedBinaryPath;
657
+ } catch (error) {
658
+ console.error('Error downloading probe binary:', error);
659
+ throw error;
660
+ }
661
+ }