Bug Fixes Part 3

Also included refactoring of the current Remote repository system to make the codebase easier to maintain
This commit is contained in:
Netkas 2022-12-17 06:58:35 -05:00
parent 722c99879e
commit 61146d1b1d
13 changed files with 752 additions and 415 deletions

View file

@ -323,6 +323,21 @@
*/ */
const UnsupportedProjectTypeException = -1762; const UnsupportedProjectTypeException = -1762;
/**
* @see UnsupportedArchiveException
*/
const UnsupportedArchiveException = -1763;
/**
* @see ArchiveException
*/
const ArchiveException = -1764;
/**
* @see PackageFetchException
*/
const PackageFetchException = -1765;
/** /**
* All the exception codes from NCC * All the exception codes from NCC
*/ */
@ -386,6 +401,9 @@
self::GitTagsException, self::GitTagsException,
self::AuthenticationException, self::AuthenticationException,
self::NotSupportedException, self::NotSupportedException,
self::UnsupportedProjectTypeException self::UnsupportedProjectTypeException,
self::UnsupportedArchiveException,
self::ArchiveException,
self::PackageFetchException
]; ];
} }

View file

@ -47,7 +47,6 @@
use ncc\Utilities\PathFinder; use ncc\Utilities\PathFinder;
use ncc\Utilities\Resolver; use ncc\Utilities\Resolver;
use ncc\Utilities\RuntimeCache; use ncc\Utilities\RuntimeCache;
use ncc\Utilities\Validate;
use SplFileInfo; use SplFileInfo;
class ComposerSourceBuiltin implements ServiceSourceInterface class ComposerSourceBuiltin implements ServiceSourceInterface
@ -277,15 +276,7 @@
{ {
if (array_key_exists($package_name, $version_map)) if (array_key_exists($package_name, $version_map))
{ {
$version = $version_map[$package_name]; return Functions::convertToSemVer($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 '1.0.0'; return '1.0.0';
@ -335,7 +326,7 @@
$dependency = new ProjectConfiguration\Dependency(); $dependency = new ProjectConfiguration\Dependency();
$dependency->Name = $package_name; $dependency->Name = $package_name;
$dependency->SourceType = DependencySourceType::Local; $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'; $dependency->Source = $package_name . '.ncc';
$project_configuration->Build->addDependency($dependency); $project_configuration->Build->addDependency($dependency);
} }
@ -359,22 +350,6 @@
return $project_configuration; 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 * Gets the applicable options configured for composer
* *

View file

@ -2,12 +2,9 @@
namespace ncc\Classes\GithubExtension; namespace ncc\Classes\GithubExtension;
use Exception;
use ncc\Abstracts\HttpRequestType; use ncc\Abstracts\HttpRequestType;
use ncc\Abstracts\Versions; use ncc\Abstracts\Versions;
use ncc\Classes\GitClient;
use ncc\Classes\HttpClient; use ncc\Classes\HttpClient;
use ncc\Classes\NccExtension\PackageCompiler;
use ncc\Exceptions\AuthenticationException; use ncc\Exceptions\AuthenticationException;
use ncc\Exceptions\GithubServiceException; use ncc\Exceptions\GithubServiceException;
use ncc\Exceptions\GitlabServiceException; use ncc\Exceptions\GitlabServiceException;
@ -18,134 +15,28 @@
use ncc\Objects\DefinedRemoteSource; use ncc\Objects\DefinedRemoteSource;
use ncc\Objects\HttpRequest; use ncc\Objects\HttpRequest;
use ncc\Objects\RemotePackageInput; use ncc\Objects\RemotePackageInput;
use ncc\Objects\RepositoryQueryResults;
use ncc\Objects\Vault\Entry; use ncc\Objects\Vault\Entry;
use ncc\ThirdParty\jelix\Version\VersionComparator; use ncc\ThirdParty\jelix\Version\VersionComparator;
use ncc\Utilities\Console;
use ncc\Utilities\Functions; use ncc\Utilities\Functions;
use ncc\Utilities\Validate;
class GithubService implements RepositorySourceInterface 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. * Returns the git repository url of the repository, versions cannot be specified.
* *
* @param RemotePackageInput $packageInput * @param RemotePackageInput $packageInput
* @param DefinedRemoteSource $definedRemoteSource * @param DefinedRemoteSource $definedRemoteSource
* @param Entry|null $entry * @param Entry|null $entry
* @return string * @return RepositoryQueryResults
* @throws AuthenticationException
* @throws GithubServiceException * @throws GithubServiceException
* @throws GitlabServiceException * @throws GitlabServiceException
* @throws AuthenticationException
* @throws HttpException * @throws HttpException
* @throws MalformedJsonException * @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(); $httpRequest = new HttpRequest();
$protocol = ($definedRemoteSource->SSL ? "https" : "http"); $protocol = ($definedRemoteSource->SSL ? "https" : "http");
@ -153,23 +44,17 @@
$owner_f = str_ireplace(".", "%2F", $owner_f); $owner_f = str_ireplace(".", "%2F", $owner_f);
$repository = urlencode($packageInput->Package); $repository = urlencode($packageInput->Package);
$httpRequest->Url = $protocol . '://' . $definedRemoteSource->Host . "/repos/$owner_f/$repository"; $httpRequest->Url = $protocol . '://' . $definedRemoteSource->Host . "/repos/$owner_f/$repository";
$httpRequest->Type = HttpRequestType::GET; $response_decoded = self::getJsonResponse($httpRequest, $entry);
$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); $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 $query;
return
$response_decoded['git_url'] ??
$response_decoded['clone_url'] ??
$response_decoded['ssh_url'] ??
throw new GithubServiceException('Failed to fetch the repository URL.');
} }
/** /**
@ -178,7 +63,7 @@
* @param RemotePackageInput $packageInput * @param RemotePackageInput $packageInput
* @param DefinedRemoteSource $definedRemoteSource * @param DefinedRemoteSource $definedRemoteSource
* @param Entry|null $entry * @param Entry|null $entry
* @return string * @return RepositoryQueryResults
* @throws AuthenticationException * @throws AuthenticationException
* @throws GithubServiceException * @throws GithubServiceException
* @throws GitlabServiceException * @throws GitlabServiceException
@ -186,42 +71,16 @@
* @throws MalformedJsonException * @throws MalformedJsonException
* @throws VersionNotFoundException * @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); return self::processReleases($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'];
} }
/** /**
* @param RemotePackageInput $packageInput * @param RemotePackageInput $packageInput
* @param DefinedRemoteSource $definedRemoteSource * @param DefinedRemoteSource $definedRemoteSource
* @param Entry|null $entry * @param Entry|null $entry
* @return string * @return RepositoryQueryResults
* @throws AuthenticationException * @throws AuthenticationException
* @throws GithubServiceException * @throws GithubServiceException
* @throws GitlabServiceException * @throws GitlabServiceException
@ -229,43 +88,9 @@
* @throws MalformedJsonException * @throws MalformedJsonException
* @throws VersionNotFoundException * @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); return self::processReleases($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;
} }
/** /**
@ -289,17 +114,7 @@
$owner_f = str_ireplace(".", "%2F", $owner_f); $owner_f = str_ireplace(".", "%2F", $owner_f);
$repository = urlencode($packageInput->Package); $repository = urlencode($packageInput->Package);
$httpRequest->Url = $protocol . '://' . $definedRemoteSource->Host . "/repos/$owner_f/$repository/releases"; $httpRequest->Url = $protocol . '://' . $definedRemoteSource->Host . "/repos/$owner_f/$repository/releases";
$httpRequest->Type = HttpRequestType::GET; $response_decoded = self::getJsonResponse($httpRequest, $entry);
$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);
if(count($response_decoded) == 0) if(count($response_decoded) == 0)
return []; return [];
@ -307,20 +122,24 @@
$return = []; $return = [];
foreach($response_decoded as $release) foreach($response_decoded as $release)
{ {
// Make the tag_name version friendly $query_results = new RepositoryQueryResults();
$release_version = str_replace('v', '', $release['tag_name']); $query_results->Version = Functions::convertToSemVer($release['tag_name']);
$return[$release_version] = [ $query_results->ReleaseName = $release['name'];
'url' => ($release['zipball_url'] ?? $release['tarball_url'] ?? null) $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'])) if(isset($release['assets']))
{ {
foreach($release['assets'] as $asset) foreach($release['assets'] as $asset)
{ {
if(self::parseAsset($asset) !== null) $parsed_asset = self::parseAsset($asset);
$return[$release_version]['package'] = $asset['browser_download_url']; if($parsed_asset !== null)
$query_results->Files->PackageUrl = $parsed_asset;
} }
} }
$return[$query_results->Version] = $query_results;
} }
return $return; return $return;
@ -343,4 +162,98 @@
return null; 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];
}
} }

View file

@ -2,11 +2,8 @@
namespace ncc\Classes\GitlabExtension; namespace ncc\Classes\GitlabExtension;
use Exception;
use ncc\Abstracts\Versions; use ncc\Abstracts\Versions;
use ncc\Classes\GitClient;
use ncc\Classes\HttpClient; use ncc\Classes\HttpClient;
use ncc\Classes\NccExtension\PackageCompiler;
use ncc\Exceptions\AuthenticationException; use ncc\Exceptions\AuthenticationException;
use ncc\Exceptions\GitlabServiceException; use ncc\Exceptions\GitlabServiceException;
use ncc\Exceptions\HttpException; use ncc\Exceptions\HttpException;
@ -17,88 +14,13 @@
use ncc\Objects\DefinedRemoteSource; use ncc\Objects\DefinedRemoteSource;
use ncc\Objects\HttpRequest; use ncc\Objects\HttpRequest;
use ncc\Objects\RemotePackageInput; use ncc\Objects\RemotePackageInput;
use ncc\Objects\RepositoryQueryResults;
use ncc\Objects\Vault\Entry; use ncc\Objects\Vault\Entry;
use ncc\ThirdParty\jelix\Version\VersionComparator; use ncc\ThirdParty\jelix\Version\VersionComparator;
use ncc\Utilities\Console;
use ncc\Utilities\Functions; use ncc\Utilities\Functions;
class GitlabService implements RepositorySourceInterface 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. * Attempts to return the gitRepositoryUrl of a release, cannot specify a version.
* This needs to be done using git * This needs to be done using git
@ -106,13 +28,13 @@
* @param RemotePackageInput $packageInput * @param RemotePackageInput $packageInput
* @param DefinedRemoteSource $definedRemoteSource * @param DefinedRemoteSource $definedRemoteSource
* @param Entry|null $entry * @param Entry|null $entry
* @return string * @return RepositoryQueryResults
* @throws AuthenticationException
* @throws GitlabServiceException * @throws GitlabServiceException
* @throws HttpException * @throws HttpException
* @throws MalformedJsonException * @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(); $httpRequest = new HttpRequest();
$protocol = ($definedRemoteSource->SSL ? "https" : "http"); $protocol = ($definedRemoteSource->SSL ? "https" : "http");
@ -130,10 +52,14 @@
$response_decoded = Functions::loadJson($response->Body, Functions::FORCE_ARRAY); $response_decoded = Functions::loadJson($response->Body, Functions::FORCE_ARRAY);
return $query = new RepositoryQueryResults();
$response_decoded['http_url_to_repo'] ?? $query->Files->GitSshUrl = ($response_decoded['ssh_url_to_repo'] ?? null);
$response_decoded['ssh_url_to_repo'] ?? $query->Files->GitHttpUrl = ($response_decoded['http_url_to_repo'] ?? null);
throw new GitlabServiceException('Failed to fetch the repository URL.'); $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 RemotePackageInput $packageInput
* @param DefinedRemoteSource $definedRemoteSource * @param DefinedRemoteSource $definedRemoteSource
* @param Entry|null $entry * @param Entry|null $entry
* @return string * @return RepositoryQueryResults
* @throws AuthenticationException * @throws AuthenticationException
* @throws GitlabServiceException * @throws GitlabServiceException
* @throws HttpException * @throws HttpException
* @throws MalformedJsonException * @throws MalformedJsonException
* @throws VersionNotFoundException * @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); $releases = self::getReleases($packageInput->Vendor, $packageInput->Package, $definedRemoteSource, $entry);
if(count($releases) === 0) if(count($releases) === 0)
throw new VersionNotFoundException('No releases found for the given repository.'); throw new VersionNotFoundException('No releases found for the given repository.');
// Query the latest package only
if($packageInput->Version == Versions::Latest) if($packageInput->Version == Versions::Latest)
{ {
$latest_version = null; $latest_version = null;
foreach($releases as $version => $url) foreach($releases as $release)
{ {
if($latest_version == null) if($latest_version == null)
{ {
$latest_version = $version; $latest_version = $release->Version;
continue; continue;
} }
if(VersionComparator::compareVersion($version, $latest_version) == 1) if(VersionComparator::compareVersion($release->Version, $latest_version) == 1)
$latest_version = $version; $latest_version = $release->Version;
} }
return $releases[$latest_version]; return $releases[$latest_version];
} }
// Query a specific version
if(!isset($releases[$packageInput->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 RemotePackageInput $packageInput
* @param DefinedRemoteSource $definedRemoteSource * @param DefinedRemoteSource $definedRemoteSource
* @param Entry|null $entry * @param Entry|null $entry
* @return string * @return RepositoryQueryResults
* @throws NotSupportedException * @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)); throw new NotSupportedException(sprintf('The given repository source "%s" does not support ncc packages.', $definedRemoteSource->Host));
} }
@ -230,25 +182,39 @@
$return = []; $return = [];
foreach($response_decoded as $release) foreach($response_decoded as $release)
{ {
// Make the tag_name version friendly $query_results = new RepositoryQueryResults();
$release_version = str_replace('v', '', $release['tag_name']); $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(isset($release['assets']) && isset($release['assets']['sources']))
{ {
if(count($release['assets']['sources']) > 0) 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) foreach($release['assets']['sources'] as $source)
{ {
if($source['format'] == 'zip') 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; break;
} }
} }
} }
} }
$return[$query_results->Version] = $query_results;
} }
return $return; return $return;

View file

@ -5,10 +5,14 @@
use Exception; use Exception;
use ncc\Abstracts\CompilerExtensions; use ncc\Abstracts\CompilerExtensions;
use ncc\Abstracts\ConstantReferences; use ncc\Abstracts\ConstantReferences;
use ncc\Abstracts\DefinedRemoteSourceType;
use ncc\Abstracts\LogLevel; use ncc\Abstracts\LogLevel;
use ncc\Abstracts\Options\BuildConfigurationValues; use ncc\Abstracts\Options\BuildConfigurationValues;
use ncc\Abstracts\ProjectType; use ncc\Abstracts\ProjectType;
use ncc\Classes\ComposerExtension\ComposerSourceBuiltin; 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\Classes\PhpExtension\PhpCompiler;
use ncc\CLI\Main; use ncc\CLI\Main;
use ncc\Exceptions\AccessDeniedException; use ncc\Exceptions\AccessDeniedException;
@ -17,17 +21,24 @@
use ncc\Exceptions\FileNotFoundException; use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\IOException; use ncc\Exceptions\IOException;
use ncc\Exceptions\MalformedJsonException; use ncc\Exceptions\MalformedJsonException;
use ncc\Exceptions\PackageFetchException;
use ncc\Exceptions\PackagePreparationFailedException; use ncc\Exceptions\PackagePreparationFailedException;
use ncc\Exceptions\ProjectConfigurationNotFoundException; use ncc\Exceptions\ProjectConfigurationNotFoundException;
use ncc\Exceptions\UnsupportedCompilerExtensionException; use ncc\Exceptions\UnsupportedCompilerExtensionException;
use ncc\Exceptions\UnsupportedProjectTypeException; use ncc\Exceptions\UnsupportedProjectTypeException;
use ncc\Exceptions\UnsupportedRemoteSourceTypeException;
use ncc\Exceptions\UnsupportedRunnerException; use ncc\Exceptions\UnsupportedRunnerException;
use ncc\Interfaces\CompilerInterface; use ncc\Interfaces\CompilerInterface;
use ncc\Interfaces\RepositorySourceInterface;
use ncc\Managers\ProjectManager; use ncc\Managers\ProjectManager;
use ncc\ncc; use ncc\ncc;
use ncc\Objects\DefinedRemoteSource;
use ncc\Objects\Package; use ncc\Objects\Package;
use ncc\Objects\ProjectConfiguration; use ncc\Objects\ProjectConfiguration;
use ncc\Objects\ProjectConfiguration\Assembly; 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\ThirdParty\Symfony\Filesystem\Filesystem;
use ncc\Utilities\Console; use ncc\Utilities\Console;
use ncc\Utilities\Functions; use ncc\Utilities\Functions;
@ -96,10 +107,13 @@
* @throws BuildException * @throws BuildException
* @throws UnsupportedProjectTypeException * @throws UnsupportedProjectTypeException
*/ */
public static function tryCompile(string $path): string public static function tryCompile(string $path, ?string $version=null): string
{ {
$project_type = Resolver::detectProjectType($path); $project_type = Resolver::detectProjectType($path);
if($version !== null)
$version = Functions::convertToSemVer($version);
try try
{ {
if($project_type->ProjectType == ProjectType::Composer) if($project_type->ProjectType == ProjectType::Composer)
@ -108,6 +122,7 @@
if($project_type->ProjectType == ProjectType::Ncc) if($project_type->ProjectType == ProjectType::Ncc)
{ {
$project_manager = new ProjectManager($project_type->ProjectPath); $project_manager = new ProjectManager($project_type->ProjectPath);
$project_manager->getProjectConfiguration()->Assembly->Version = $version;
return $project_manager->build(); return $project_manager->build();
} }
} }
@ -119,6 +134,7 @@
throw new UnsupportedProjectTypeException('The project type \'' . $project_type->ProjectType . '\' is not supported'); throw new UnsupportedProjectTypeException('The project type \'' . $project_type->ProjectType . '\' is not supported');
} }
/** /**
* Compiles the execution policies of the package * Compiles the execution policies of the package
* *

View file

@ -0,0 +1,15 @@
<?php
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class ArchiveException extends Exception
{
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::ArchiveException, $previous);
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace ncc\Exceptions;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class PackageFetchException extends \Exception
{
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::PackageFetchException, $previous);
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class UnsupportedArchiveException extends Exception
{
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::UnsupportedArchiveException, $previous);
$this->message = $message;
}
}

View file

@ -4,6 +4,7 @@
use ncc\Objects\DefinedRemoteSource; use ncc\Objects\DefinedRemoteSource;
use ncc\Objects\RemotePackageInput; use ncc\Objects\RemotePackageInput;
use ncc\Objects\RepositoryQueryResults;
use ncc\Objects\Vault\Entry; use ncc\Objects\Vault\Entry;
interface RepositorySourceInterface interface RepositorySourceInterface
@ -14,9 +15,9 @@
* @param RemotePackageInput $packageInput * @param RemotePackageInput $packageInput
* @param DefinedRemoteSource $definedRemoteSource * @param DefinedRemoteSource $definedRemoteSource
* @param Entry|null $entry * @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. * Returns the release url of the repository, versions can be specified.
@ -24,9 +25,9 @@
* @param RemotePackageInput $packageInput * @param RemotePackageInput $packageInput
* @param DefinedRemoteSource $definedRemoteSource * @param DefinedRemoteSource $definedRemoteSource
* @param Entry|null $entry * @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 * Returns the download URL of the pre-compiled .ncc package if available
@ -34,7 +35,7 @@
* @param RemotePackageInput $packageInput * @param RemotePackageInput $packageInput
* @param DefinedRemoteSource $definedRemoteSource * @param DefinedRemoteSource $definedRemoteSource
* @param Entry|null $entry * @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;
} }

View file

@ -8,15 +8,13 @@
use ncc\Abstracts\BuiltinRemoteSourceType; use ncc\Abstracts\BuiltinRemoteSourceType;
use ncc\Abstracts\CompilerExtensions; use ncc\Abstracts\CompilerExtensions;
use ncc\Abstracts\ConstantReferences; use ncc\Abstracts\ConstantReferences;
use ncc\Abstracts\DefinedRemoteSourceType;
use ncc\Abstracts\DependencySourceType; use ncc\Abstracts\DependencySourceType;
use ncc\Abstracts\LogLevel; use ncc\Abstracts\LogLevel;
use ncc\Abstracts\RemoteSourceType; use ncc\Abstracts\RemoteSourceType;
use ncc\Abstracts\Scopes; use ncc\Abstracts\Scopes;
use ncc\Abstracts\Versions; use ncc\Abstracts\Versions;
use ncc\Classes\ComposerExtension\ComposerSourceBuiltin; use ncc\Classes\ComposerExtension\ComposerSourceBuiltin;
use ncc\Classes\GithubExtension\GithubService; use ncc\Classes\GitClient;
use ncc\Classes\GitlabExtension\GitlabService;
use ncc\Classes\NccExtension\PackageCompiler; use ncc\Classes\NccExtension\PackageCompiler;
use ncc\Classes\PhpExtension\PhpInstaller; use ncc\Classes\PhpExtension\PhpInstaller;
use ncc\CLI\Main; use ncc\CLI\Main;
@ -28,13 +26,14 @@
use ncc\Exceptions\MissingDependencyException; use ncc\Exceptions\MissingDependencyException;
use ncc\Exceptions\NotImplementedException; use ncc\Exceptions\NotImplementedException;
use ncc\Exceptions\PackageAlreadyInstalledException; use ncc\Exceptions\PackageAlreadyInstalledException;
use ncc\Exceptions\PackageFetchException;
use ncc\Exceptions\PackageLockException; use ncc\Exceptions\PackageLockException;
use ncc\Exceptions\PackageNotFoundException; use ncc\Exceptions\PackageNotFoundException;
use ncc\Exceptions\PackageParsingException; use ncc\Exceptions\PackageParsingException;
use ncc\Exceptions\UnsupportedCompilerExtensionException; use ncc\Exceptions\UnsupportedCompilerExtensionException;
use ncc\Exceptions\UnsupportedRemoteSourceTypeException;
use ncc\Exceptions\UnsupportedRunnerException; use ncc\Exceptions\UnsupportedRunnerException;
use ncc\Exceptions\VersionNotFoundException; use ncc\Exceptions\VersionNotFoundException;
use ncc\Interfaces\RepositorySourceInterface;
use ncc\Objects\DefinedRemoteSource; use ncc\Objects\DefinedRemoteSource;
use ncc\Objects\InstallationPaths; use ncc\Objects\InstallationPaths;
use ncc\Objects\Package; use ncc\Objects\Package;
@ -43,9 +42,11 @@
use ncc\Objects\ProjectConfiguration\Dependency; use ncc\Objects\ProjectConfiguration\Dependency;
use ncc\Objects\RemotePackageInput; use ncc\Objects\RemotePackageInput;
use ncc\Objects\Vault\Entry; use ncc\Objects\Vault\Entry;
use ncc\ThirdParty\jelix\Version\VersionComparator;
use ncc\ThirdParty\Symfony\Filesystem\Filesystem; use ncc\ThirdParty\Symfony\Filesystem\Filesystem;
use ncc\ThirdParty\theseer\DirectoryScanner\DirectoryScanner; use ncc\ThirdParty\theseer\DirectoryScanner\DirectoryScanner;
use ncc\Utilities\Console; use ncc\Utilities\Console;
use ncc\Utilities\Functions;
use ncc\Utilities\IO; use ncc\Utilities\IO;
use ncc\Utilities\PathFinder; use ncc\Utilities\PathFinder;
use ncc\Utilities\Resolver; use ncc\Utilities\Resolver;
@ -80,6 +81,7 @@
* Installs a local package onto the system * Installs a local package onto the system
* *
* @param string $package_path * @param string $package_path
* @param Entry|null $entry
* @return string * @return string
* @throws AccessDeniedException * @throws AccessDeniedException
* @throws FileNotFoundException * @throws FileNotFoundException
@ -340,75 +342,122 @@
/** /**
* @param string $source * @param string $source
* @param Entry|null $auth_entry * @param Entry|null $entry
* @return string * @return string
* @throws InstallationException * @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) if($input->Source == null)
throw new InstallationException('No source specified'); throw new PackageFetchException('No source specified');
if($input->Package == null)
if($parsed_source->Package == null) throw new PackageFetchException('No package specified');
throw new InstallationException('No package specified'); if($input->Version == null)
$input->Version = Versions::Latest;
if($parsed_source->Version == null)
$parsed_source->Version = Versions::Latest;
$remote_source_type = Resolver::detectRemoteSourceType($parsed_source->Source);
$remote_source_type = Resolver::detectRemoteSourceType($input->Source);
if($remote_source_type == RemoteSourceType::Builtin) if($remote_source_type == RemoteSourceType::Builtin)
{ {
switch($parsed_source->Source) switch($input->Source)
{ {
case BuiltinRemoteSourceType::Composer: case BuiltinRemoteSourceType::Composer:
try try
{ {
return ComposerSourceBuiltin::fetch($parsed_source); return ComposerSourceBuiltin::fetch($input);
} }
catch(Exception $e) 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: 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) if($remote_source_type == RemoteSourceType::Defined)
{ {
$remote_source_manager = new RemoteSourcesManager(); $remote_source_manager = new RemoteSourcesManager();
$remote_source = $remote_source_manager->getRemoteSource($parsed_source->Source); $source = $remote_source_manager->getRemoteSource($input->Source);
if($remote_source == null) if($source == null)
throw new InstallationException('Remote source ' . $parsed_source->Source . ' is not defined'); throw new InstallationException('Remote source ' . $input->Source . ' is not defined');
/** @var RepositorySourceInterface $remote_service_client */ $repositoryQueryResults = Functions::getRepositoryQueryResults($input, $source, $entry);
$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'),
};
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) * Installs a package from a source syntax (vendor/package=version@source)
* *
* @param string $source * @param string $source
* @param Entry|null $entry
* @return string * @return string
* @throws InstallationException * @throws InstallationException
*/ */
@ -429,6 +478,7 @@
* @param Dependency $dependency * @param Dependency $dependency
* @param Package $package * @param Package $package
* @param string $package_path * @param string $package_path
* @param Entry|null $entry
* @return void * @return void
* @throws AccessDeniedException * @throws AccessDeniedException
* @throws FileNotFoundException * @throws FileNotFoundException

View file

@ -0,0 +1,46 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects;
use ncc\Objects\RepositoryQueryResults\Files;
class RepositoryQueryResults
{
/**
* A collection of files that are available for download
*
* @var Files
*/
public $Files;
/**
* The version of the package returned by the query
*
* @var string|null
*/
public $Version;
/**
* The name of the release returned by the query
*
* @var string|null
*/
public $ReleaseName;
/**
* The description of the release returned by the query
*
* @var string|null
*/
public $ReleaseDescription;
/**
* Public Constructor
*/
public function __construct()
{
$this->Files = new Files();
}
}

View file

@ -0,0 +1,50 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\RepositoryQueryResults;
class Files
{
/**
* The URL that points to a pre-compiled .ncc package
*
* @var string|null
*/
public $PackageUrl;
/**
* The URL that points to a archived version of the source code
*
* @var string|null
*/
public $SourceUrl;
/**
* The URL that points to a tarball archive of the repository
*
* @var string|null
*/
public $TarballUrl;
/**
* The URL that points to a zip archive of the repository
*
* @var string
*/
public $ZipballUrl;
/**
* The URL that points to the repository's source code
*
* @var string
*/
public $GitHttpUrl;
/**
* The URL that points to the repository's source code
*
* @var string
*/
public $GitSshUrl;
}

View file

@ -4,10 +4,13 @@
use Exception; use Exception;
use ncc\Abstracts\AuthenticationType; use ncc\Abstracts\AuthenticationType;
use ncc\Abstracts\DefinedRemoteSourceType;
use ncc\Abstracts\HttpRequestType; use ncc\Abstracts\HttpRequestType;
use ncc\Abstracts\Runners; use ncc\Abstracts\Runners;
use ncc\Abstracts\Scopes; use ncc\Abstracts\Scopes;
use ncc\Classes\BashExtension\BashRunner; use ncc\Classes\BashExtension\BashRunner;
use ncc\Classes\GithubExtension\GithubService;
use ncc\Classes\GitlabExtension\GitlabService;
use ncc\Classes\HttpClient; use ncc\Classes\HttpClient;
use ncc\Classes\LuaExtension\LuaRunner; use ncc\Classes\LuaExtension\LuaRunner;
use ncc\Classes\PerlExtension\PerlRunner; use ncc\Classes\PerlExtension\PerlRunner;
@ -16,6 +19,7 @@
use ncc\Classes\PythonExtension\Python3Runner; use ncc\Classes\PythonExtension\Python3Runner;
use ncc\Classes\PythonExtension\PythonRunner; use ncc\Classes\PythonExtension\PythonRunner;
use ncc\Exceptions\AccessDeniedException; use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\ArchiveException;
use ncc\Exceptions\AuthenticationException; use ncc\Exceptions\AuthenticationException;
use ncc\Exceptions\FileNotFoundException; use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\GitlabServiceException; use ncc\Exceptions\GitlabServiceException;
@ -23,23 +27,29 @@
use ncc\Exceptions\InvalidScopeException; use ncc\Exceptions\InvalidScopeException;
use ncc\Exceptions\IOException; use ncc\Exceptions\IOException;
use ncc\Exceptions\MalformedJsonException; use ncc\Exceptions\MalformedJsonException;
use ncc\Exceptions\UnsupportedArchiveException;
use ncc\Exceptions\UnsupportedRunnerException; use ncc\Exceptions\UnsupportedRunnerException;
use ncc\Managers\ConfigurationManager; use ncc\Managers\ConfigurationManager;
use ncc\Managers\CredentialManager; use ncc\Managers\CredentialManager;
use ncc\Managers\PackageLockManager; use ncc\Managers\PackageLockManager;
use ncc\Objects\CliHelpSection; use ncc\Objects\CliHelpSection;
use ncc\Objects\ComposerJson; use ncc\Objects\ComposerJson;
use ncc\Objects\DefinedRemoteSource;
use ncc\Objects\HttpRequest; use ncc\Objects\HttpRequest;
use ncc\Objects\Package\ExecutionUnit; use ncc\Objects\Package\ExecutionUnit;
use ncc\Objects\ProjectConfiguration\ExecutionPolicy; use ncc\Objects\ProjectConfiguration\ExecutionPolicy;
use ncc\Objects\RemotePackageInput;
use ncc\Objects\RepositoryQueryResults;
use ncc\Objects\RepositoryQueryResults\Files;
use ncc\Objects\Vault\Entry; use ncc\Objects\Vault\Entry;
use ncc\ThirdParty\jelix\Version\Parser; use ncc\ThirdParty\jelix\Version\Parser;
use ncc\ThirdParty\jelix\Version\VersionComparator;
use ncc\ThirdParty\Symfony\Filesystem\Filesystem; use ncc\ThirdParty\Symfony\Filesystem\Filesystem;
use PharData; use ncc\ThirdParty\Symfony\Process\ExecutableFinder;
use ncc\ThirdParty\Symfony\Process\Process;
use RecursiveDirectoryIterator; use RecursiveDirectoryIterator;
use RecursiveIteratorIterator; use RecursiveIteratorIterator;
use Throwable; use Throwable;
use ZipArchive;
/** /**
* @author Zi Xing Narrakas * @author Zi Xing Narrakas
@ -584,40 +594,96 @@
/** /**
* @param string $path * @param string $path
* @return string * @return string|null
* @throws Exception * @throws ArchiveException
* @throws UnsupportedArchiveException
*/ */
public static function extractArchive(string $path): ?string public static function extractArchive(string $path): ?string
{ {
$finfo = finfo_open(FILEINFO_MIME_TYPE); $executable_finder = new ExecutableFinder();
$mimeType = finfo_file($finfo, $path); $unzip_executable = $executable_finder->find('unzip');
finfo_close($finfo); $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; $supportedTypes = array_merge($supportedTypes, [
$res = $zip->open($path); 'application/zip',
if ($res === TRUE) '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)); Console::out('unzip executable not found. ZIP archives will not be supported.');
$zip->close(); RuntimeCache::set('warning_zip_shown', true);
return dirname($path);
} }
} }
if ($mimeType == 'application/x-tar' || $mimeType == 'application/tar')
if($tar_executable !== null)
{ {
$phar = new PharData($path); $supportedTypes = array_merge($supportedTypes, [
$phar->extractTo(dirname($path), null, true); 'application/x-tar',
return dirname($path); 'application/x-gzip',
'application/x-bzip2',
'application/x-xz'
]);
} }
elseif ($mimeType == 'application/x-gzip' || $mimeType == 'application/gzip') else
{ {
$phar = new PharData($path); if(RuntimeCache::get('warning_tar_shown') !== true)
$phar->decompress(); {
return dirname($path); 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 * @param $version
* @return string * @return string
*/ */
public static function convertToSemVer($version) public static function convertToSemVer($version): string
{ {
$parts = explode('.', $version); if(stripos($version, 'v') === 0)
$major = $parts[0]; $version = substr($version, 1);
$minor = $parts[1]; if(!Validate::version($version))
$patch = $parts[2]; {
$buildmetadata = $parts[3]; $parts = explode('.', $version);
$major = (string)null;
$minor = (string)null;
$patch = (string)null;
// Assemble the SemVer compatible string $buildmetadata = (string)null;
$semver = "$major.$minor.$patch+$buildmetadata"; 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;
}
} }