diff --git a/src/ncc/Abstracts/ExceptionCodes.php b/src/ncc/Abstracts/ExceptionCodes.php index 60d7412..38b32da 100644 --- a/src/ncc/Abstracts/ExceptionCodes.php +++ b/src/ncc/Abstracts/ExceptionCodes.php @@ -323,6 +323,21 @@ */ const UnsupportedProjectTypeException = -1762; + /** + * @see UnsupportedArchiveException + */ + const UnsupportedArchiveException = -1763; + + /** + * @see ArchiveException + */ + const ArchiveException = -1764; + + /** + * @see PackageFetchException + */ + const PackageFetchException = -1765; + /** * All the exception codes from NCC */ @@ -386,6 +401,9 @@ self::GitTagsException, self::AuthenticationException, self::NotSupportedException, - self::UnsupportedProjectTypeException + self::UnsupportedProjectTypeException, + self::UnsupportedArchiveException, + self::ArchiveException, + self::PackageFetchException ]; } \ No newline at end of file diff --git a/src/ncc/Classes/ComposerExtension/ComposerSourceBuiltin.php b/src/ncc/Classes/ComposerExtension/ComposerSourceBuiltin.php index eee3abe..a210837 100644 --- a/src/ncc/Classes/ComposerExtension/ComposerSourceBuiltin.php +++ b/src/ncc/Classes/ComposerExtension/ComposerSourceBuiltin.php @@ -47,7 +47,6 @@ use ncc\Utilities\PathFinder; use ncc\Utilities\Resolver; use ncc\Utilities\RuntimeCache; - use ncc\Utilities\Validate; use SplFileInfo; class ComposerSourceBuiltin implements ServiceSourceInterface @@ -277,15 +276,7 @@ { if (array_key_exists($package_name, $version_map)) { - $version = $version_map[$package_name]; - if(stripos($version, 'v') === 0) - $version = substr($version, 1); - if(!Validate::version($version)) - $version = Functions::convertToSemVer($version); - if(!Validate::version($version)) - return '1.0.0'; - - return $version; + return Functions::convertToSemVer($version_map[$package_name]); } return '1.0.0'; @@ -335,7 +326,7 @@ $dependency = new ProjectConfiguration\Dependency(); $dependency->Name = $package_name; $dependency->SourceType = DependencySourceType::Local; - $dependency->Version = self::versionMap($item->PackageName, $version_map);; + $dependency->Version = self::versionMap($item->PackageName, $version_map); $dependency->Source = $package_name . '.ncc'; $project_configuration->Build->addDependency($dependency); } @@ -359,22 +350,6 @@ return $project_configuration; } - /** - * Extracts a version if available from the input - * - * @param string $input - * @return string|null - */ - private static function extractVersion(string $input): ?string - { - if (stripos($input, ':')) - { - return explode(':', $input)[1]; - } - - return null; - } - /** * Gets the applicable options configured for composer * diff --git a/src/ncc/Classes/GithubExtension/GithubService.php b/src/ncc/Classes/GithubExtension/GithubService.php index 745eed2..440f4c9 100644 --- a/src/ncc/Classes/GithubExtension/GithubService.php +++ b/src/ncc/Classes/GithubExtension/GithubService.php @@ -2,12 +2,9 @@ namespace ncc\Classes\GithubExtension; - use Exception; use ncc\Abstracts\HttpRequestType; use ncc\Abstracts\Versions; - use ncc\Classes\GitClient; use ncc\Classes\HttpClient; - use ncc\Classes\NccExtension\PackageCompiler; use ncc\Exceptions\AuthenticationException; use ncc\Exceptions\GithubServiceException; use ncc\Exceptions\GitlabServiceException; @@ -18,134 +15,28 @@ use ncc\Objects\DefinedRemoteSource; use ncc\Objects\HttpRequest; use ncc\Objects\RemotePackageInput; + use ncc\Objects\RepositoryQueryResults; use ncc\Objects\Vault\Entry; use ncc\ThirdParty\jelix\Version\VersionComparator; - use ncc\Utilities\Console; use ncc\Utilities\Functions; + use ncc\Utilities\Validate; class GithubService implements RepositorySourceInterface { - - /** - * Attempts to fetch the .ncc package from a remote source, optionally attempts to compile - * the package if it cannot find a pre-compiled version. - * - * Priority of fetching: - * - Pre-compiled version - * - Source code of specified release - * - Git repository (checkout specified release) - * - Git repository master branch (If version is not specified or set to latest) - * - * @param RemotePackageInput $packageInput - * @param DefinedRemoteSource $definedRemoteSource - * @param Entry|null $entry - * @return string - * @throws GithubServiceException - */ - public static function fetch(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry): string - { - // Check if there is a pre-compiled version of the package available - try - { - Console::outVerbose(sprintf('Attempting to fetch pre-compiled package from %s', $definedRemoteSource->Host)); - $ncc_package = self::getNccPackage($packageInput, $definedRemoteSource, $entry); - } - catch(Exception $e) - { - $ncc_package = null; - unset($e); - } - - if($ncc_package !== null) - { - try - { - return Functions::downloadGitServiceFile($ncc_package, $entry); - } - catch(Exception $e) - { - throw new GithubServiceException(sprintf('Failed to download pre-compiled package from %s', $definedRemoteSource->Host), $e); - } - } - - // Check if the specified version is a release - try - { - Console::outVerbose(sprintf('Attempting to fetch source code from %s', $definedRemoteSource->Host)); - $release_url = self::getRelease($packageInput, $definedRemoteSource, $entry); - } - catch(Exception $e) - { - $release_url = null; - unset($e); - } - - // If the specified version is a release, download the source code - if($release_url !== null) - { - try - { - $release_file = Functions::downloadGitServiceFile($release_url, $entry); - $project_path = Functions::extractArchive($release_file); - return PackageCompiler::tryCompile($project_path); - } - catch(Exception $e) - { - throw new GithubServiceException(sprintf('Failed to download release from %s', $definedRemoteSource->Host), $e); - } - } - - try - { - Console::outVerbose(sprintf('Attempting to fetch git repository from %s', $definedRemoteSource->Host)); - $git_url = self::fetchGitUri($packageInput, $definedRemoteSource, $entry); - } - catch(Exception $e) - { - $git_url = null; - unset($e); - } - - if($git_url !== null) - { - try - { - $project_path = GitClient::cloneRepository($git_url); - - foreach(GitClient::getTags($project_path) as $tag) - { - $tag = str_replace('v', '', $tag); - if(VersionComparator::compareVersion($tag, $packageInput->Version) === 0) - { - GitClient::checkout($project_path, $tag); - return PackageCompiler::tryCompile($project_path); - } - } - } - catch(Exception $e) - { - throw new GithubServiceException(sprintf('Failed to clone git repository from %s', $definedRemoteSource->Host), $e); - } - } - - throw new GithubServiceException('Unable to fetch package from remote source'); - } - - /** * Returns the git repository url of the repository, versions cannot be specified. * * @param RemotePackageInput $packageInput * @param DefinedRemoteSource $definedRemoteSource * @param Entry|null $entry - * @return string + * @return RepositoryQueryResults + * @throws AuthenticationException * @throws GithubServiceException * @throws GitlabServiceException - * @throws AuthenticationException * @throws HttpException * @throws MalformedJsonException */ - public static function fetchGitUri(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): string + public static function getGitRepository(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): RepositoryQueryResults { $httpRequest = new HttpRequest(); $protocol = ($definedRemoteSource->SSL ? "https" : "http"); @@ -153,23 +44,17 @@ $owner_f = str_ireplace(".", "%2F", $owner_f); $repository = urlencode($packageInput->Package); $httpRequest->Url = $protocol . '://' . $definedRemoteSource->Host . "/repos/$owner_f/$repository"; - $httpRequest->Type = HttpRequestType::GET; - $httpRequest = Functions::prepareGitServiceRequest($httpRequest, $entry, false); - $httpRequest->Headers[] = 'X-GitHub-Api-Version: 2022-11-28'; - $httpRequest->Headers[] = 'Accept: application/vnd.github+json'; + $response_decoded = self::getJsonResponse($httpRequest, $entry); - $response = HttpClient::request($httpRequest, true); + $query = new RepositoryQueryResults(); + $query->Files->GitSshUrl = ($response_decoded['ssh_url'] ?? null); + $query->Files->GitHttpUrl = ($response_decoded['clone_url'] ?? null); + $query->Version = Functions::convertToSemVer($response_decoded['default_branch'] ?? null); + $query->ReleaseDescription = ($response_decoded['description'] ?? null); + $query->ReleaseName = ($response_decoded['name'] ?? null); - if($response->StatusCode != 200) - throw new GithubServiceException(sprintf('Failed to fetch releases for the given repository. Status code: %s', $response->StatusCode)); - $response_decoded = Functions::loadJson($response->Body, Functions::FORCE_ARRAY); - - return - $response_decoded['git_url'] ?? - $response_decoded['clone_url'] ?? - $response_decoded['ssh_url'] ?? - throw new GithubServiceException('Failed to fetch the repository URL.'); + return $query; } /** @@ -178,7 +63,7 @@ * @param RemotePackageInput $packageInput * @param DefinedRemoteSource $definedRemoteSource * @param Entry|null $entry - * @return string + * @return RepositoryQueryResults * @throws AuthenticationException * @throws GithubServiceException * @throws GitlabServiceException @@ -186,42 +71,16 @@ * @throws MalformedJsonException * @throws VersionNotFoundException */ - public static function getRelease(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): string + public static function getRelease(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): RepositoryQueryResults { - $releases = self::getReleases($packageInput, $definedRemoteSource, $entry); - - if(count($releases) === 0) - throw new VersionNotFoundException('No releases found for the given repository.'); - - if($packageInput->Version == Versions::Latest) - { - $latest_version = null; - foreach($releases as $version => $url) - { - if($latest_version == null) - { - $latest_version = $version; - continue; - } - - if(VersionComparator::compareVersion($version, $latest_version) == 1) - $latest_version = $version; - } - - return $releases[$latest_version]['url']; - } - - if(!isset($releases[$packageInput->Version])) - throw new VersionNotFoundException(sprintf('The given version "%s" does not exist.', $packageInput->Version)); - - return $releases[$packageInput->Version]['url']; + return self::processReleases($packageInput, $definedRemoteSource, $entry); } /** * @param RemotePackageInput $packageInput * @param DefinedRemoteSource $definedRemoteSource * @param Entry|null $entry - * @return string + * @return RepositoryQueryResults * @throws AuthenticationException * @throws GithubServiceException * @throws GitlabServiceException @@ -229,43 +88,9 @@ * @throws MalformedJsonException * @throws VersionNotFoundException */ - public static function getNccPackage(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): string + public static function getNccPackage(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): RepositoryQueryResults { - $releases = self::getReleases($packageInput, $definedRemoteSource, $entry); - - if(count($releases) === 0) - throw new VersionNotFoundException('No releases found for the given repository.'); - - if($packageInput->Version == Versions::Latest) - { - $latest_version = null; - foreach($releases as $version => $url) - { - if($latest_version == null) - { - $latest_version = $version; - continue; - } - - if(VersionComparator::compareVersion($version, $latest_version) == 1) - $latest_version = $version; - } - - $return = $releases[$latest_version]['package'] ?? null; - if($return === null) - throw new VersionNotFoundException('No releases found for the given repository.'); - - return $return; - } - - if(!isset($releases[$packageInput->Version])) - throw new VersionNotFoundException(sprintf('The given version "%s" does not exist.', $packageInput->Version)); - - $return = $releases[$packageInput->Version]['package'] ?? null; - if($return === null) - throw new VersionNotFoundException('No releases found for the given repository.'); - - return $return; + return self::processReleases($packageInput, $definedRemoteSource, $entry); } /** @@ -289,17 +114,7 @@ $owner_f = str_ireplace(".", "%2F", $owner_f); $repository = urlencode($packageInput->Package); $httpRequest->Url = $protocol . '://' . $definedRemoteSource->Host . "/repos/$owner_f/$repository/releases"; - $httpRequest->Type = HttpRequestType::GET; - $httpRequest = Functions::prepareGitServiceRequest($httpRequest, $entry, false); - $httpRequest->Headers[] = 'X-GitHub-Api-Version: 2022-11-28'; - $httpRequest->Headers[] = 'Accept: application/vnd.github+json'; - - $response = HttpClient::request($httpRequest, true); - - if($response->StatusCode != 200) - throw new GithubServiceException(sprintf('Failed to fetch releases for the given repository. Status code: %s', $response->StatusCode)); - - $response_decoded = Functions::loadJson($response->Body, Functions::FORCE_ARRAY); + $response_decoded = self::getJsonResponse($httpRequest, $entry); if(count($response_decoded) == 0) return []; @@ -307,20 +122,24 @@ $return = []; foreach($response_decoded as $release) { - // Make the tag_name version friendly - $release_version = str_replace('v', '', $release['tag_name']); - $return[$release_version] = [ - 'url' => ($release['zipball_url'] ?? $release['tarball_url'] ?? null) - ]; + $query_results = new RepositoryQueryResults(); + $query_results->Version = Functions::convertToSemVer($release['tag_name']); + $query_results->ReleaseName = $release['name']; + $query_results->ReleaseDescription = $release['body']; + $query_results->Files->ZipballUrl = ($release['zipball_url'] ?? null); + $query_results->Files->TarballUrl = ($release['tarball_url'] ?? null); if(isset($release['assets'])) { foreach($release['assets'] as $asset) { - if(self::parseAsset($asset) !== null) - $return[$release_version]['package'] = $asset['browser_download_url']; + $parsed_asset = self::parseAsset($asset); + if($parsed_asset !== null) + $query_results->Files->PackageUrl = $parsed_asset; } } + + $return[$query_results->Version] = $query_results; } return $return; @@ -343,4 +162,98 @@ return null; } + + /** + * @param HttpRequest $httpRequest + * @param Entry|null $entry + * @return array + * @throws AuthenticationException + * @throws GithubServiceException + * @throws GitlabServiceException + * @throws HttpException + * @throws MalformedJsonException + */ + private static function getJsonResponse(HttpRequest $httpRequest, ?Entry $entry): array + { + $httpRequest->Type = HttpRequestType::GET; + $httpRequest = Functions::prepareGitServiceRequest($httpRequest, $entry, false); + $httpRequest->Headers[] = 'X-GitHub-Api-Version: 2022-11-28'; + $httpRequest->Headers[] = 'Accept: application/vnd.github+json'; + + $response = HttpClient::request($httpRequest, true); + + if ($response->StatusCode != 200) + throw new GithubServiceException(sprintf('Failed to fetch releases for the given repository. Status code: %s', $response->StatusCode)); + + $response_decoded = Functions::loadJson($response->Body, Functions::FORCE_ARRAY); + return $response_decoded; + } + + /** + * @param RemotePackageInput $packageInput + * @param DefinedRemoteSource $definedRemoteSource + * @param Entry|null $entry + * @return mixed + * @throws AuthenticationException + * @throws GithubServiceException + * @throws GitlabServiceException + * @throws HttpException + * @throws MalformedJsonException + * @throws VersionNotFoundException + */ + private static function processReleases(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry): mixed + { + $releases = self::getReleases($packageInput, $definedRemoteSource, $entry); + + if (count($releases) === 0) + throw new VersionNotFoundException('No releases found for the given repository.'); + + if ($packageInput->Version == Versions::Latest) + { + $latest_version = null; + foreach ($releases as $release) + { + if ($latest_version == null) + { + $latest_version = $release->Version; + continue; + } + + if (VersionComparator::compareVersion($release->Version, $latest_version) == 1) + $latest_version = $release->Version; + } + + return $releases[$latest_version]; + } + + // Query a specific version + if (!isset($releases[$packageInput->Version])) + { + // Find the closest thing to the requested version + $selected_version = null; + foreach ($releases as $version => $url) + { + if ($selected_version == null) + { + $selected_version = $version; + continue; + } + + if (VersionComparator::compareVersion($version, $packageInput->Version) == 1) + $selected_version = $version; + } + + if ($selected_version == null) + throw new VersionNotFoundException('No releases found for the given repository.'); + } + else + { + $selected_version = $packageInput->Version; + } + + if (!isset($releases[$selected_version])) + throw new VersionNotFoundException(sprintf('No releases found for the given repository. (Selected version: %s)', $selected_version)); + + return $releases[$selected_version]; + } } \ No newline at end of file diff --git a/src/ncc/Classes/GitlabExtension/GitlabService.php b/src/ncc/Classes/GitlabExtension/GitlabService.php index b398d5d..feda673 100644 --- a/src/ncc/Classes/GitlabExtension/GitlabService.php +++ b/src/ncc/Classes/GitlabExtension/GitlabService.php @@ -2,11 +2,8 @@ namespace ncc\Classes\GitlabExtension; - use Exception; use ncc\Abstracts\Versions; - use ncc\Classes\GitClient; use ncc\Classes\HttpClient; - use ncc\Classes\NccExtension\PackageCompiler; use ncc\Exceptions\AuthenticationException; use ncc\Exceptions\GitlabServiceException; use ncc\Exceptions\HttpException; @@ -17,88 +14,13 @@ use ncc\Objects\DefinedRemoteSource; use ncc\Objects\HttpRequest; use ncc\Objects\RemotePackageInput; + use ncc\Objects\RepositoryQueryResults; use ncc\Objects\Vault\Entry; use ncc\ThirdParty\jelix\Version\VersionComparator; - use ncc\Utilities\Console; use ncc\Utilities\Functions; class GitlabService implements RepositorySourceInterface { - - /** - * Attempts to fetch the requested package from the Gitlab repository, and returns the pre-compiled package - * - * @param RemotePackageInput $packageInput - * @param DefinedRemoteSource $definedRemoteSource - * @param Entry|null $entry - * @return string - * @throws GitlabServiceException - */ - public static function fetch(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry): string - { - // Check if the specified version is a release - try - { - Console::outVerbose(sprintf('Attempting to fetch source code from %s', $definedRemoteSource->Host)); - $release_url = self::getRelease($packageInput, $definedRemoteSource, $entry); - } - catch(Exception $e) - { - $release_url = null; - unset($e); - } - - // If the specified version is a release, download the source code - if($release_url !== null) - { - try - { - $release_file = Functions::downloadGitServiceFile($release_url, $entry); - $project_path = Functions::extractArchive($release_file); - return PackageCompiler::tryCompile($project_path); - } - catch(Exception $e) - { - throw new GitlabServiceException(sprintf('Failed to download release from %s', $definedRemoteSource->Host), $e); - } - } - - try - { - Console::outVerbose(sprintf('Attempting to fetch git repository from %s', $definedRemoteSource->Host)); - $git_url = self::fetchGitUri($packageInput, $definedRemoteSource, $entry); - } - catch(Exception $e) - { - $git_url = null; - unset($e); - } - - if($git_url !== null) - { - try - { - $project_path = GitClient::cloneRepository($git_url); - - foreach(GitClient::getTags($project_path) as $tag) - { - $tag = str_replace('v', '', $tag); - if(VersionComparator::compareVersion($tag, $packageInput->Version) === 0) - { - GitClient::checkout($project_path, $tag); - return PackageCompiler::tryCompile($project_path); - } - } - } - catch(Exception $e) - { - throw new GitlabServiceException(sprintf('Failed to clone git repository from %s', $definedRemoteSource->Host), $e); - } - } - - throw new GitlabServiceException('Unable to fetch package from remote source'); - } - /** * Attempts to return the gitRepositoryUrl of a release, cannot specify a version. * This needs to be done using git @@ -106,13 +28,13 @@ * @param RemotePackageInput $packageInput * @param DefinedRemoteSource $definedRemoteSource * @param Entry|null $entry - * @return string + * @return RepositoryQueryResults + * @throws AuthenticationException * @throws GitlabServiceException * @throws HttpException * @throws MalformedJsonException - * @throws AuthenticationException */ - public static function fetchGitUri(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry=null): string + public static function getGitRepository(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry=null): RepositoryQueryResults { $httpRequest = new HttpRequest(); $protocol = ($definedRemoteSource->SSL ? "https" : "http"); @@ -130,10 +52,14 @@ $response_decoded = Functions::loadJson($response->Body, Functions::FORCE_ARRAY); - return - $response_decoded['http_url_to_repo'] ?? - $response_decoded['ssh_url_to_repo'] ?? - throw new GitlabServiceException('Failed to fetch the repository URL.'); + $query = new RepositoryQueryResults(); + $query->Files->GitSshUrl = ($response_decoded['ssh_url_to_repo'] ?? null); + $query->Files->GitHttpUrl = ($response_decoded['http_url_to_repo'] ?? null); + $query->Version = Functions::convertToSemVer($response_decoded['default_branch']) ?? null; + $query->ReleaseDescription = ($response_decoded['description'] ?? null); + $query->ReleaseName = ($response_decoded['name'] ?? null); + + return $query; } /** @@ -142,52 +68,78 @@ * @param RemotePackageInput $packageInput * @param DefinedRemoteSource $definedRemoteSource * @param Entry|null $entry - * @return string + * @return RepositoryQueryResults * @throws AuthenticationException * @throws GitlabServiceException * @throws HttpException * @throws MalformedJsonException * @throws VersionNotFoundException */ - public static function getRelease(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): string + public static function getRelease(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): RepositoryQueryResults { $releases = self::getReleases($packageInput->Vendor, $packageInput->Package, $definedRemoteSource, $entry); if(count($releases) === 0) throw new VersionNotFoundException('No releases found for the given repository.'); + // Query the latest package only if($packageInput->Version == Versions::Latest) { $latest_version = null; - foreach($releases as $version => $url) + foreach($releases as $release) { if($latest_version == null) { - $latest_version = $version; + $latest_version = $release->Version; continue; } - if(VersionComparator::compareVersion($version, $latest_version) == 1) - $latest_version = $version; + if(VersionComparator::compareVersion($release->Version, $latest_version) == 1) + $latest_version = $release->Version; } return $releases[$latest_version]; } + // Query a specific version if(!isset($releases[$packageInput->Version])) - throw new VersionNotFoundException(sprintf('The given version "%s" does not exist.', $packageInput->Version)); + { + // Find the closest thing to the requested version + $selected_version = null; + foreach($releases as $version => $url) + { + if($selected_version == null) + { + $selected_version = $version; + continue; + } - return $releases[$packageInput->Version]; + if(VersionComparator::compareVersion($version, $packageInput->Version) == 1) + $selected_version = $version; + } + + if($selected_version == null) + throw new VersionNotFoundException('No releases found for the given repository.'); + } + else + { + $selected_version = $packageInput->Version; + } + + if(!isset($releases[$selected_version])) + throw new VersionNotFoundException(sprintf('No releases found for the given repository. (Selected version: %s)', $selected_version)); + + return $releases[$selected_version]; } /** * @param RemotePackageInput $packageInput * @param DefinedRemoteSource $definedRemoteSource * @param Entry|null $entry - * @return string + * @return RepositoryQueryResults * @throws NotSupportedException */ - public static function getNccPackage(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): string + public static function getNccPackage(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): RepositoryQueryResults { throw new NotSupportedException(sprintf('The given repository source "%s" does not support ncc packages.', $definedRemoteSource->Host)); } @@ -230,25 +182,39 @@ $return = []; foreach($response_decoded as $release) { - // Make the tag_name version friendly - $release_version = str_replace('v', '', $release['tag_name']); + $query_results = new RepositoryQueryResults(); + $query_results->ReleaseName = ($release['name'] ?? null); + $query_results->ReleaseDescription = ($release['description'] ?? null); + $query_results->Version = Functions::convertToSemVer($release['tag_name']); if(isset($release['assets']) && isset($release['assets']['sources'])) { if(count($release['assets']['sources']) > 0) { - // Use the first source as the download url, if a tar.gz file is available, use that instead. - $return[$release_version] = $release['assets']['sources'][0]['url']; foreach($release['assets']['sources'] as $source) { if($source['format'] == 'zip') { - $return[$release_version] = $source['url']; + $query_results->Files->ZipballUrl = $source['url']; + break; + } + + if($source['format'] == 'tar.gz') + { + $query_results->Files->ZipballUrl = $source['url']; + break; + } + + if($source['format'] == 'ncc') + { + $query_results->Files->PackageUrl = $source['url']; break; } } } } + + $return[$query_results->Version] = $query_results; } return $return; diff --git a/src/ncc/Classes/NccExtension/PackageCompiler.php b/src/ncc/Classes/NccExtension/PackageCompiler.php index 08c96bb..5e643bb 100644 --- a/src/ncc/Classes/NccExtension/PackageCompiler.php +++ b/src/ncc/Classes/NccExtension/PackageCompiler.php @@ -5,10 +5,14 @@ use Exception; use ncc\Abstracts\CompilerExtensions; use ncc\Abstracts\ConstantReferences; + use ncc\Abstracts\DefinedRemoteSourceType; use ncc\Abstracts\LogLevel; use ncc\Abstracts\Options\BuildConfigurationValues; use ncc\Abstracts\ProjectType; use ncc\Classes\ComposerExtension\ComposerSourceBuiltin; + use ncc\Classes\GitClient; + use ncc\Classes\GithubExtension\GithubService; + use ncc\Classes\GitlabExtension\GitlabService; use ncc\Classes\PhpExtension\PhpCompiler; use ncc\CLI\Main; use ncc\Exceptions\AccessDeniedException; @@ -17,17 +21,24 @@ use ncc\Exceptions\FileNotFoundException; use ncc\Exceptions\IOException; use ncc\Exceptions\MalformedJsonException; + use ncc\Exceptions\PackageFetchException; use ncc\Exceptions\PackagePreparationFailedException; use ncc\Exceptions\ProjectConfigurationNotFoundException; use ncc\Exceptions\UnsupportedCompilerExtensionException; use ncc\Exceptions\UnsupportedProjectTypeException; + use ncc\Exceptions\UnsupportedRemoteSourceTypeException; use ncc\Exceptions\UnsupportedRunnerException; use ncc\Interfaces\CompilerInterface; + use ncc\Interfaces\RepositorySourceInterface; use ncc\Managers\ProjectManager; use ncc\ncc; + use ncc\Objects\DefinedRemoteSource; use ncc\Objects\Package; use ncc\Objects\ProjectConfiguration; use ncc\Objects\ProjectConfiguration\Assembly; + use ncc\Objects\RemotePackageInput; + use ncc\Objects\Vault\Entry; + use ncc\ThirdParty\jelix\Version\VersionComparator; use ncc\ThirdParty\Symfony\Filesystem\Filesystem; use ncc\Utilities\Console; use ncc\Utilities\Functions; @@ -96,10 +107,13 @@ * @throws BuildException * @throws UnsupportedProjectTypeException */ - public static function tryCompile(string $path): string + public static function tryCompile(string $path, ?string $version=null): string { $project_type = Resolver::detectProjectType($path); + if($version !== null) + $version = Functions::convertToSemVer($version); + try { if($project_type->ProjectType == ProjectType::Composer) @@ -108,6 +122,7 @@ if($project_type->ProjectType == ProjectType::Ncc) { $project_manager = new ProjectManager($project_type->ProjectPath); + $project_manager->getProjectConfiguration()->Assembly->Version = $version; return $project_manager->build(); } } @@ -119,6 +134,7 @@ throw new UnsupportedProjectTypeException('The project type \'' . $project_type->ProjectType . '\' is not supported'); } + /** * Compiles the execution policies of the package * diff --git a/src/ncc/Exceptions/ArchiveException.php b/src/ncc/Exceptions/ArchiveException.php new file mode 100644 index 0000000..a7e7a22 --- /dev/null +++ b/src/ncc/Exceptions/ArchiveException.php @@ -0,0 +1,15 @@ +message = $message; + } + } \ No newline at end of file diff --git a/src/ncc/Interfaces/RepositorySourceInterface.php b/src/ncc/Interfaces/RepositorySourceInterface.php index 25d7ee4..09cc39c 100644 --- a/src/ncc/Interfaces/RepositorySourceInterface.php +++ b/src/ncc/Interfaces/RepositorySourceInterface.php @@ -4,6 +4,7 @@ use ncc\Objects\DefinedRemoteSource; use ncc\Objects\RemotePackageInput; + use ncc\Objects\RepositoryQueryResults; use ncc\Objects\Vault\Entry; interface RepositorySourceInterface @@ -14,9 +15,9 @@ * @param RemotePackageInput $packageInput * @param DefinedRemoteSource $definedRemoteSource * @param Entry|null $entry - * @return string + * @return RepositoryQueryResults */ - public static function fetchGitUri(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry=null): string; + public static function getGitRepository(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry=null): RepositoryQueryResults; /** * Returns the release url of the repository, versions can be specified. @@ -24,9 +25,9 @@ * @param RemotePackageInput $packageInput * @param DefinedRemoteSource $definedRemoteSource * @param Entry|null $entry - * @return string + * @return RepositoryQueryResults */ - public static function getRelease(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): string; + public static function getRelease(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): RepositoryQueryResults; /** * Returns the download URL of the pre-compiled .ncc package if available @@ -34,7 +35,7 @@ * @param RemotePackageInput $packageInput * @param DefinedRemoteSource $definedRemoteSource * @param Entry|null $entry - * @return string + * @return RepositoryQueryResults */ - public static function getNccPackage(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): string; + public static function getNccPackage(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry = null): RepositoryQueryResults; } \ No newline at end of file diff --git a/src/ncc/Managers/PackageManager.php b/src/ncc/Managers/PackageManager.php index a62d311..b2a5e71 100644 --- a/src/ncc/Managers/PackageManager.php +++ b/src/ncc/Managers/PackageManager.php @@ -8,15 +8,13 @@ use ncc\Abstracts\BuiltinRemoteSourceType; use ncc\Abstracts\CompilerExtensions; use ncc\Abstracts\ConstantReferences; - use ncc\Abstracts\DefinedRemoteSourceType; use ncc\Abstracts\DependencySourceType; use ncc\Abstracts\LogLevel; use ncc\Abstracts\RemoteSourceType; use ncc\Abstracts\Scopes; use ncc\Abstracts\Versions; use ncc\Classes\ComposerExtension\ComposerSourceBuiltin; - use ncc\Classes\GithubExtension\GithubService; - use ncc\Classes\GitlabExtension\GitlabService; + use ncc\Classes\GitClient; use ncc\Classes\NccExtension\PackageCompiler; use ncc\Classes\PhpExtension\PhpInstaller; use ncc\CLI\Main; @@ -28,13 +26,14 @@ use ncc\Exceptions\MissingDependencyException; use ncc\Exceptions\NotImplementedException; use ncc\Exceptions\PackageAlreadyInstalledException; + use ncc\Exceptions\PackageFetchException; use ncc\Exceptions\PackageLockException; use ncc\Exceptions\PackageNotFoundException; use ncc\Exceptions\PackageParsingException; use ncc\Exceptions\UnsupportedCompilerExtensionException; + use ncc\Exceptions\UnsupportedRemoteSourceTypeException; use ncc\Exceptions\UnsupportedRunnerException; use ncc\Exceptions\VersionNotFoundException; - use ncc\Interfaces\RepositorySourceInterface; use ncc\Objects\DefinedRemoteSource; use ncc\Objects\InstallationPaths; use ncc\Objects\Package; @@ -43,9 +42,11 @@ use ncc\Objects\ProjectConfiguration\Dependency; use ncc\Objects\RemotePackageInput; use ncc\Objects\Vault\Entry; + use ncc\ThirdParty\jelix\Version\VersionComparator; use ncc\ThirdParty\Symfony\Filesystem\Filesystem; use ncc\ThirdParty\theseer\DirectoryScanner\DirectoryScanner; use ncc\Utilities\Console; + use ncc\Utilities\Functions; use ncc\Utilities\IO; use ncc\Utilities\PathFinder; use ncc\Utilities\Resolver; @@ -80,6 +81,7 @@ * Installs a local package onto the system * * @param string $package_path + * @param Entry|null $entry * @return string * @throws AccessDeniedException * @throws FileNotFoundException @@ -340,75 +342,122 @@ /** * @param string $source - * @param Entry|null $auth_entry + * @param Entry|null $entry * @return string * @throws InstallationException + * @throws NotImplementedException + * @throws PackageFetchException */ - public function fetchFromSource(string $source, ?Entry $auth_entry=null): string + public function fetchFromSource(string $source, ?Entry $entry=null): string { - $parsed_source = new RemotePackageInput($source); + $input = new RemotePackageInput($source); - if($parsed_source->Source == null) - throw new InstallationException('No source specified'); - - if($parsed_source->Package == null) - throw new InstallationException('No package specified'); - - if($parsed_source->Version == null) - $parsed_source->Version = Versions::Latest; - - $remote_source_type = Resolver::detectRemoteSourceType($parsed_source->Source); + if($input->Source == null) + throw new PackageFetchException('No source specified'); + if($input->Package == null) + throw new PackageFetchException('No package specified'); + if($input->Version == null) + $input->Version = Versions::Latest; + $remote_source_type = Resolver::detectRemoteSourceType($input->Source); if($remote_source_type == RemoteSourceType::Builtin) { - switch($parsed_source->Source) + switch($input->Source) { case BuiltinRemoteSourceType::Composer: try { - return ComposerSourceBuiltin::fetch($parsed_source); + return ComposerSourceBuiltin::fetch($input); } catch(Exception $e) { - throw new InstallationException('Cannot fetch package from composer source, ' . $e->getMessage(), $e); + throw new PackageFetchException('Cannot fetch package from composer source, ' . $e->getMessage(), $e); } default: - throw new InstallationException('Builtin source type ' . $parsed_source->Source . ' is not implemented'); + throw new NotImplementedException('Builtin source type ' . $input->Source . ' is not implemented'); } } if($remote_source_type == RemoteSourceType::Defined) { $remote_source_manager = new RemoteSourcesManager(); - $remote_source = $remote_source_manager->getRemoteSource($parsed_source->Source); - if($remote_source == null) - throw new InstallationException('Remote source ' . $parsed_source->Source . ' is not defined'); + $source = $remote_source_manager->getRemoteSource($input->Source); + if($source == null) + throw new InstallationException('Remote source ' . $input->Source . ' is not defined'); - /** @var RepositorySourceInterface $remote_service_client */ - $remote_service_client = match ($remote_source->Type) { - DefinedRemoteSourceType::Gitlab => GitlabService::class, - DefinedRemoteSourceType::Github => GithubService::class, - default => throw new InstallationException('Remote source type ' . $remote_source->Type . ' is not implemented'), - }; + $repositoryQueryResults = Functions::getRepositoryQueryResults($input, $source, $entry); - try + if($repositoryQueryResults->Files->ZipballUrl !== null) { - return $remote_service_client::fetch($parsed_source, $remote_source, $auth_entry); + try + { + $archive = Functions::downloadGitServiceFile($repositoryQueryResults->Files->ZipballUrl, $entry); + return PackageCompiler::tryCompile(Functions::extractArchive($archive), $repositoryQueryResults->Version); + } + catch(Exception $e) + { + unset($e); + } } - catch(Exception $e) + + if($repositoryQueryResults->Files->TarballUrl !== null) { - throw new InstallationException('Cannot fetch package from remote source, ' . $e->getMessage(), $e); + try + { + $archive = Functions::downloadGitServiceFile($repositoryQueryResults->Files->TarballUrl, $entry); + return PackageCompiler::tryCompile(Functions::extractArchive($archive), $repositoryQueryResults->Version); + } + catch(Exception $e) + { + unset($e); + } } + + if($repositoryQueryResults->Files->PackageUrl !== null) + { + try + { + return Functions::downloadGitServiceFile($repositoryQueryResults->Files->PackageUrl, $entry); + } + catch(Exception $e) + { + unset($e); + } + } + + if($repositoryQueryResults->Files->GitHttpUrl !== null || $repositoryQueryResults->Files->GitSshUrl !== null) + { + try + { + $git_repository = GitClient::cloneRepository($repositoryQueryResults->Files->GitHttpUrl ?? $repositoryQueryResults->Files->GitSshUrl); + + foreach(GitClient::getTags($git_repository) as $tag) + { + if(VersionComparator::compareVersion($tag, $repositoryQueryResults->Version) === 0) + { + GitClient::checkout($git_repository, $tag); + return PackageCompiler::tryCompile($git_repository, $repositoryQueryResults->Version); + } + } + } + catch(Exception $e) + { + unset($e); + } + } + + throw new PackageFetchException(sprintf('Failed to fetch package \'%s\'', $input->Package)); } - throw new InstallationException(sprintf('Unknown remote source type %s', $remote_source_type)); + throw new PackageFetchException(sprintf('Unknown remote source type %s', $remote_source_type)); } /** * Installs a package from a source syntax (vendor/package=version@source) * * @param string $source + * @param Entry|null $entry * @return string * @throws InstallationException */ @@ -429,6 +478,7 @@ * @param Dependency $dependency * @param Package $package * @param string $package_path + * @param Entry|null $entry * @return void * @throws AccessDeniedException * @throws FileNotFoundException diff --git a/src/ncc/Objects/RepositoryQueryResults.php b/src/ncc/Objects/RepositoryQueryResults.php new file mode 100644 index 0000000..a55ab9b --- /dev/null +++ b/src/ncc/Objects/RepositoryQueryResults.php @@ -0,0 +1,46 @@ +Files = new Files(); + } + } \ No newline at end of file diff --git a/src/ncc/Objects/RepositoryQueryResults/Files.php b/src/ncc/Objects/RepositoryQueryResults/Files.php new file mode 100644 index 0000000..2cda2d2 --- /dev/null +++ b/src/ncc/Objects/RepositoryQueryResults/Files.php @@ -0,0 +1,50 @@ +find('unzip'); + $tar_executable = $executable_finder->find('tar'); + $out_path = dirname($path); + $filesystem = new Filesystem(); - if ($mimeType == 'application/zip') + if(!$filesystem->exists($out_path)) + $filesystem->mkdir($out_path); + + RuntimeCache::setFileAsTemporary($out_path); + + $mimeType = mime_content_type($path); + $supportedTypes = []; + + if($unzip_executable !== null) { - $zip = new ZipArchive; - $res = $zip->open($path); - if ($res === TRUE) + $supportedTypes = array_merge($supportedTypes, [ + 'application/zip', + 'application/x-zip', + 'application/x-zip-compressed', + 'application/octet-stream', + 'application/x-compress', + 'application/x-compressed', + 'multipart/x-zip' + ]); + } + else + { + if(RuntimeCache::get('warning_zip_shown') !== true) { - $zip->extractTo(dirname($path)); - $zip->close(); - return dirname($path); + Console::out('unzip executable not found. ZIP archives will not be supported.'); + RuntimeCache::set('warning_zip_shown', true); } } - if ($mimeType == 'application/x-tar' || $mimeType == 'application/tar') + + if($tar_executable !== null) { - $phar = new PharData($path); - $phar->extractTo(dirname($path), null, true); - return dirname($path); + $supportedTypes = array_merge($supportedTypes, [ + 'application/x-tar', + 'application/x-gzip', + 'application/x-bzip2', + 'application/x-xz' + ]); } - elseif ($mimeType == 'application/x-gzip' || $mimeType == 'application/gzip') + else { - $phar = new PharData($path); - $phar->decompress(); - return dirname($path); + if(RuntimeCache::get('warning_tar_shown') !== true) + { + Console::outWarning('tar executable not found. TAR archives will not be supported.'); + RuntimeCache::set('warning_tar_shown', true); + } } - return null; + if (!in_array($mimeType, $supportedTypes)) + throw new UnsupportedArchiveException("Unsupported archive type: $mimeType"); + + switch($mimeType) + { + case 'application/zip': + $command = [$unzip_executable, $path, '-d', $out_path]; + break; + case 'application/x-tar': + $command = [$tar_executable, '--verbose', '-xf', $path, '-C', $out_path]; + break; + case 'application/x-gzip': + $command = [$tar_executable, '--verbose', '-xzf', $path, '-C', $out_path]; + break; + case 'application/x-bzip2': + $command = [$tar_executable, '--verbose', '-xjf', $path, '-C', $out_path]; + break; + } + + Console::out("Extracting archive $path"); + $process = new Process($command); + + // display the output of the command + $process->run(function ($type, $buffer) { + Console::outVerbose($buffer); + }); + + if (!$process->isSuccessful()) + throw new ArchiveException($process->getErrorOutput()); + + return $out_path; } /** @@ -649,20 +715,207 @@ * @param $version * @return string */ - public static function convertToSemVer($version) + public static function convertToSemVer($version): string { - $parts = explode('.', $version); - $major = $parts[0]; - $minor = $parts[1]; - $patch = $parts[2]; - $buildmetadata = $parts[3]; + if(stripos($version, 'v') === 0) + $version = substr($version, 1); + if(!Validate::version($version)) + { + $parts = explode('.', $version); + $major = (string)null; + $minor = (string)null; + $patch = (string)null; - // Assemble the SemVer compatible string - $semver = "$major.$minor.$patch+$buildmetadata"; + $buildmetadata = (string)null; + if(count($parts) >= 1) + $major = $parts[0]; + if(count($parts) >= 2) + $minor = $parts[1]; + if(count($parts) >= 3) + $patch = $parts[2]; - return $semver; + // Assemble the SemVer compatible string + $version = "$major.$minor.$patch"; + } + if(!Validate::version($version)) + return '1.0.0'; + + return $version; } + /** + * Returns a complete RepositoryQueryResults object + * + * @param RemotePackageInput $packageInput + * @param DefinedRemoteSource $definedRemoteSource + * @param Entry|null $entry + * @return RepositoryQueryResults + */ + public static function getRepositoryQueryResults(RemotePackageInput $packageInput, DefinedRemoteSource $definedRemoteSource, ?Entry $entry): RepositoryQueryResults + { + $results = new RepositoryQueryResults(); + switch($definedRemoteSource->Type) + { + case DefinedRemoteSourceType::Github: + $source = GithubService::class; + break; + case DefinedRemoteSourceType::Gitlab: + $source = GitlabService::class; + break; + + default: + return $results; + } + + // Check if the specified version is a release + try + { + Console::outVerbose(sprintf('Attempting to fetch source code from %s', $definedRemoteSource->Host)); + $release_results = $source::getRelease($packageInput, $definedRemoteSource, $entry); + } + catch(Exception $e) + { + $release_results = null; + unset($e); + } + + // If the specified version is a release, download the source code + if($release_results !== null) + { + $results->ReleaseName = ($release_results->ReleaseName ?? null); + $results->ReleaseDescription = ($release_results->ReleaseDescription ?? null); + $results->Files = self::mergeFilesResults($release_results->Files, ($results->Files ?? null)); + if($release_results->Version !== null) + $results->Version = $release_results->Version; + } + + try + { + $git_results = $source::getGitRepository($packageInput, $definedRemoteSource, $entry); + } + catch(Exception $e) + { + $git_results = null; + unset($e); + } + + if($git_results !== null) + { + if($results->ReleaseName == null) + { + $results->ReleaseName = ($git_results->ReleaseName ?? null); + } + elseif($git_results->ReleaseName !== null) + { + if(strlen($git_results->ReleaseName) > strlen($results->ReleaseName)) + $results->ReleaseName = $git_results->ReleaseName; + } + + if($results->ReleaseDescription == null) + { + $results->ReleaseDescription = ($git_results->ReleaseDescription ?? null); + } + elseif($git_results->ReleaseDescription !== null) + { + if(strlen($git_results->ReleaseDescription) > strlen($results->ReleaseDescription)) + $results->ReleaseDescription = $git_results->ReleaseDescription; + } + + if($results->Version == null) + { + $results->Version = ($git_results->Version ?? null); + } + elseif($git_results->Version !== null) + { + // Version compare + if(VersionComparator::compareVersion($git_results->Version, $results->Version) > 0) + $results->Version = $git_results->Version; + } + + $results->Files = self::mergeFilesResults($git_results->Files, ($results->Files ?? null)); + } + + try + { + $ncc_package_results = $source::getNccPackage($packageInput, $definedRemoteSource, $entry); + } + catch(Exception $e) + { + unset($e); + $ncc_package_results = null; + } + + if($ncc_package_results !== null) + { + if($results->ReleaseName == null) + { + $results->ReleaseName = ($ncc_package_results->ReleaseName ?? null); + } + elseif($ncc_package_results->ReleaseName !== null) + { + if(strlen($ncc_package_results->ReleaseName) > strlen($results->ReleaseName)) + $results->ReleaseName = $ncc_package_results->ReleaseName; + } + + if($results->ReleaseDescription == null) + { + $results->ReleaseDescription = ($ncc_package_results->ReleaseDescription ?? null); + } + elseif($ncc_package_results->ReleaseDescription !== null) + { + if(strlen($ncc_package_results->ReleaseDescription) > strlen($results->ReleaseDescription)) + $results->ReleaseDescription = $ncc_package_results->ReleaseDescription; + } + + if($results->Version == null) + { + $results->Version = ($ncc_package_results->Version ?? null); + } + elseif($ncc_package_results->Version !== null) + { + // Version compare + if(VersionComparator::compareVersion($ncc_package_results->Version, $results->Version) > 0) + $results->Version = $ncc_package_results->Version; + } + + $results->Files = self::mergeFilesResults($ncc_package_results->Files, ($results->Files ?? null)); + } + + return $results; + } + + /** + * Merges the given Files object with another Files object + * + * @param Files $input + * @param Files|null $selected + * @return Files + */ + private static function mergeFilesResults(RepositoryQueryResults\Files $input, ?RepositoryQueryResults\Files $selected=null): RepositoryQueryResults\Files + { + if($selected == null) + $selected = new RepositoryQueryResults\Files(); + + if($input->GitSshUrl !== null) + $selected->GitSshUrl = $input->GitSshUrl; + + if($input->GitHttpUrl !== null) + $selected->GitHttpUrl = $input->GitHttpUrl; + + if($input->SourceUrl !== null) + $selected->SourceUrl = $input->SourceUrl; + + if($input->TarballUrl !== null) + $selected->TarballUrl = $input->TarballUrl; + + if($input->ZipballUrl !== null) + $selected->ZipballUrl = $input->ZipballUrl; + + if($input->PackageUrl !== null) + $selected->PackageUrl = $input->PackageUrl; + + return $selected; + } } \ No newline at end of file