diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30bd079..ca4b347 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,11 @@ jobs: run: | choco install php --version=8.2 --params '"/ExtensionList:mbstring,curl,openssl,xml"' refreshenv + # Download CA bundle + Invoke-WebRequest -Uri https://curl.se/ca/cacert.pem -OutFile C:\tools\cacert.pem + # Configure PHP to use the CA bundle + $phpIni = php --ini | Select-String "Loaded Configuration File" | ForEach-Object { $_.Line.Split()[-1] } + Add-Content $phpIni "`nopenssl.cafile = C:\tools\cacert.pem" php -v shell: pwsh diff --git a/README.md b/README.md index d20db1b..16de321 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ php test.php - PHP 8.0 or higher - ext-json - ext-pcntl +- ext-curl ## License diff --git a/bin/platforms/darwin-amd64/volt-test b/bin/platforms/darwin-amd64/volt-test deleted file mode 100755 index b4d542f..0000000 Binary files a/bin/platforms/darwin-amd64/volt-test and /dev/null differ diff --git a/bin/platforms/darwin-arm64/volt-test b/bin/platforms/darwin-arm64/volt-test deleted file mode 100755 index fb162c3..0000000 Binary files a/bin/platforms/darwin-arm64/volt-test and /dev/null differ diff --git a/bin/platforms/linux-amd64/volt-test b/bin/platforms/linux-amd64/volt-test deleted file mode 100755 index e6ac1ce..0000000 Binary files a/bin/platforms/linux-amd64/volt-test and /dev/null differ diff --git a/bin/platforms/windows-amd64/volt-test.exe b/bin/platforms/windows-amd64/volt-test.exe deleted file mode 100755 index 8ca9f32..0000000 Binary files a/bin/platforms/windows-amd64/volt-test.exe and /dev/null differ diff --git a/composer.json b/composer.json index 402be23..e7d3df4 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,8 @@ ], "require": { "php": "^8.0", - "ext-json": "*" + "ext-json": "*", + "ext-curl": "*" }, "suggest": { "ext-pcntl": "For signal handling support on Unix-like systems" diff --git a/src/Platform.php b/src/Platform.php index 5586685..a5d08a1 100644 --- a/src/Platform.php +++ b/src/Platform.php @@ -5,66 +5,69 @@ class Platform { private const BINARY_NAME = 'volt-test'; + + private const CURRENT_VERSION = 'v0.0.1-beta'; + private const BASE_DOWNLOAD_URL = 'https://github.com/volt-test/binaries/releases/download'; private const SUPPORTED_PLATFORMS = [ - 'linux-amd64' => 'linux-amd64/volt-test', - 'darwin-amd64' => 'darwin-amd64/volt-test', - 'darwin-arm64' => 'darwin-arm64/volt-test', - 'windows-amd64' => 'windows-amd64/volt-test.exe', - 'windows-AMD64' => 'windows-amd64/volt-test.exe', + 'linux-amd64' => 'volt-test-linux-amd64', + 'darwin-amd64' => 'volt-test-darwin-amd64', + 'darwin-arm64' => 'volt-test-darwin-arm64', + 'windows-amd64' => 'volt-test-windows-amd64.exe', + 'windows-AMD64' => 'volt-test-windows-amd64.exe', ]; - public static function installBinary($testing = false): void + private static function getVendorDir(): string { - $platform = self::detectPlatform($testing); - - if (! array_key_exists($platform, self::SUPPORTED_PLATFORMS)) { - throw new \RuntimeException("Platform $platform is not supported"); - } - - $sourceFile = __DIR__ . '/../bin/platforms/' . self::SUPPORTED_PLATFORMS[$platform]; - $targetDir = self::getBinaryDir(); - $targetFile = $targetDir . '/' . basename(self::SUPPORTED_PLATFORMS[$platform]); - - if (! file_exists($sourceFile)) { - throw new \RuntimeException("Binary not found for platform: $platform"); + // First try using Composer's environment variable + if (getenv('COMPOSER_VENDOR_DIR')) { + return rtrim(getenv('COMPOSER_VENDOR_DIR'), '/\\'); } - if (! is_dir($targetDir)) { - if (! mkdir($targetDir, 0755, true)) { - throw new \RuntimeException("Failed to create directory: $targetDir"); + // Then try using Composer's home directory + if (getenv('COMPOSER_HOME')) { + $vendorDir = rtrim(getenv('COMPOSER_HOME'), '/\\') . '/vendor'; + if (is_dir($vendorDir)) { + return $vendorDir; } } - if (! copy($sourceFile, $targetFile)) { - throw new \RuntimeException("Failed to copy binary to: $targetFile"); + // Try to find vendor directory relative to current file + $paths = [ + __DIR__ . '/../../../', // From src/VoltTest to vendor + __DIR__ . '/vendor/', // Direct vendor subdirectory + dirname(__DIR__, 2) . '/vendor/', // Two levels up + dirname(__DIR__, 3) . '/vendor/', // Three levels up + dirname(__DIR__) . '/vendor/', + ]; + + foreach ($paths as $path) { + if (file_exists($path . 'autoload.php')) { + return rtrim($path, '/\\'); + } } - chmod($targetFile, 0755); - - // Create symlink in vendor/bin - $vendorBinDir = self::getVendorBinDir(); - if (! is_dir($vendorBinDir)) { - if (! mkdir($vendorBinDir, 0755, true)) { - throw new \RuntimeException("Failed to create vendor bin directory: $vendorBinDir"); + // If running as a Composer script, use the working directory + if (getenv('COMPOSER')) { + $path = getcwd() . '/vendor'; + if (is_dir($path)) { + return $path; } } - $symlinkPath = $vendorBinDir . '/' . self::BINARY_NAME; - if (file_exists($symlinkPath)) { - unlink($symlinkPath); - } + throw new \RuntimeException( + 'Could not locate Composer vendor directory. ' . + 'Please ensure you are installing through Composer.' + ); + } - if (PHP_OS_FAMILY === 'Windows') { - // Windows doesn't support symlinks by default, so we'll copy the file - if (! copy($targetFile, $symlinkPath)) { - throw new \RuntimeException("Failed to copy binary to vendor/bin: $symlinkPath"); - } - } else { - if (! symlink($targetFile, $symlinkPath)) { - throw new \RuntimeException("Failed to create symlink in vendor/bin: $symlinkPath"); - } - } + private static function getBinaryDir(): string + { + return self::getVendorDir() . '/bin'; + } + private static function getCurrentVersion(): string + { + return self::CURRENT_VERSION; } private static function detectPlatform($testing = false): string @@ -82,40 +85,131 @@ private static function detectPlatform($testing = false): string } elseif (strpos($os, 'linux') === 0) { $os = 'linux'; } + if ($arch === 'x86_64') { $arch = 'amd64'; + } elseif ($arch === 'arm64' || $arch === 'aarch64') { + $arch = 'arm64'; } return "$os-$arch"; } - private static function getBinaryDir(): string + public static function installBinary($testing = false): void { - return self::getVendorDir() . '/volt-test/bin'; - } + $platform = self::detectPlatform($testing); - private static function getVendorBinDir(): string - { - return self::getVendorDir() . '/bin'; - } + if (!array_key_exists($platform, self::SUPPORTED_PLATFORMS)) { + throw new \RuntimeException("Platform $platform is not supported"); + } - private static function getVendorDir(): string - { - // Traverse up from current directory until we find vendor directory - $dir = __DIR__; - while ($dir !== '/' && ! is_dir($dir . '/vendor')) { - $dir = dirname($dir); + $version = self::getCurrentVersion(); + $binaryName = self::SUPPORTED_PLATFORMS[$platform]; + $downloadUrl = sprintf('%s/%s/%s', self::BASE_DOWNLOAD_URL, $version, $binaryName); + + $binDir = self::getBinaryDir(); + $targetFile = $binDir . '/' . self::BINARY_NAME; + if (PHP_OS_FAMILY === 'Windows') { + $targetFile .= '.exe'; } - if (! is_dir($dir . '/vendor')) { - throw new \RuntimeException('Could not locate vendor directory'); + if (!is_dir($binDir)) { + if (!mkdir($binDir, 0755, true)) { + throw new \RuntimeException("Failed to create directory: $binDir"); + } } - return $dir . '/vendor'; + echo "Downloading VoltTest binary $version for platform: $platform\n"; + + $tempFile = tempnam(sys_get_temp_dir(), 'volt-test-download-'); + if ($tempFile === false) { + throw new \RuntimeException("Failed to create temporary file"); + } + + try { + $ch = curl_init($downloadUrl); + if ($ch === false) { + throw new \RuntimeException("Failed to initialize cURL"); + } + + $fp = fopen($tempFile, 'w'); + if ($fp === false) { + curl_close($ch); + throw new \RuntimeException("Failed to open temporary file for writing"); + } + + curl_setopt_array($ch, [ + CURLOPT_FILE => $fp, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_FAILONERROR => true, + CURLOPT_TIMEOUT => 300, + CURLOPT_CONNECTTIMEOUT => 30, + CURLOPT_HTTPHEADER => ['User-Agent: volt-test-php-sdk'] + ]); + + if (curl_exec($ch) === false) { + throw new \RuntimeException("Download failed: " . curl_error($ch)); + } + + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if ($httpCode !== 200) { + throw new \RuntimeException("HTTP request failed with status $httpCode"); + } + + curl_close($ch); + fclose($fp); + + if (!file_exists($tempFile) || filesize($tempFile) === 0) { + throw new \RuntimeException("Downloaded file is empty or missing"); + } + + if (!rename($tempFile, $targetFile)) { + throw new \RuntimeException("Failed to move downloaded binary to: $targetFile"); + } + + if (!chmod($targetFile, 0755)) { + throw new \RuntimeException("Failed to set executable permissions on binary"); + } + + file_put_contents($binDir . '/.volt-test-version', $version); + + echo "Successfully installed VoltTest binary $version to: $targetFile\n"; + } catch (\Exception $e) { + if (file_exists($tempFile)) { + unlink($tempFile); + } + throw $e; + } } public static function getBinaryPath(): string { - return self::getBinaryDir() . '/' . basename(self::SUPPORTED_PLATFORMS[self::detectPlatform()]); + $binDir = self::getBinaryDir(); + $binaryName = self::BINARY_NAME; + if (PHP_OS_FAMILY === 'Windows') { + $binaryName .= '.exe'; + } + + $binaryPath = $binDir . '/' . $binaryName; + $versionFile = $binDir . '/.volt-test-version'; + + $needsInstall = true; + + if (file_exists($binaryPath) && file_exists($versionFile)) { + try { + $currentVersion = trim(file_get_contents($versionFile)); + $latestVersion = self::getCurrentVersion(); + $needsInstall = $currentVersion !== $latestVersion; + } catch (\Exception $e) { + $needsInstall = true; + } + } + + if ($needsInstall) { + self::installBinary(); + } + + return $binaryPath; } } diff --git a/tests/Units/PlatformTest.php b/tests/Units/PlatformTest.php index 3371619..07b5558 100644 --- a/tests/Units/PlatformTest.php +++ b/tests/Units/PlatformTest.php @@ -71,7 +71,7 @@ public function testGetBinaryPath() // Assert that the binary path contains the expected directory structure $this->assertStringContainsString( - 'volt-test/bin', + '/bin', $binaryPath, "Binary path does not contain the expected directory structure" ); @@ -94,7 +94,7 @@ public function testInstallBinary() $vendorDir = $getVendorDirMethod->invoke($platform); // Assert that the binary directory is created - $binaryDir = $vendorDir . '/volt-test/bin'; + $binaryDir = $vendorDir . '/bin'; $this->assertDirectoryExists($binaryDir, "Binary directory was not created"); // Assert that the binary file exists @@ -107,7 +107,9 @@ public function testInstallBinary() // Check vendor/bin symlink or copy $vendorBinDir = $vendorDir . '/bin'; $symlinkPath = $vendorBinDir . '/volt-test'; - + if (PHP_OS_FAMILY === 'Windows') { + $symlinkPath .= '.exe'; + } $this->assertTrue( file_exists($symlinkPath) || is_link($symlinkPath), "Symlink or copy in vendor/bin directory was not created"