Initial commit
This commit is contained in:
16
README.md
16
README.md
@@ -1,6 +1,6 @@
|
||||
# Composer Package Tools
|
||||
|
||||
This is a composer plugin to create zipballs, and publish to Gitea.
|
||||
This is a composer plugin to create zipballs, and publish them to Gitea.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -11,21 +11,19 @@ $ composer require --dev noccylabs/composer-package
|
||||
$ composer require --global noccylabs/composer-package
|
||||
|
||||
# Authenticate to remotes
|
||||
$ composer package:login gitea:myserver.tld
|
||||
Username: myusername
|
||||
Token:
|
||||
$ composer package:login myserver.tld
|
||||
|
||||
# Create package by cloning into temporary directory
|
||||
$ composer package
|
||||
# Create package from working directory
|
||||
$ composer package --unclean
|
||||
$ composer package --dirty
|
||||
# Create and publish without saving zipball
|
||||
$ composer package --publish gitea:myserver.tld/myowner
|
||||
|
||||
# Publish latest zipball
|
||||
$ composer package:publish gitea:myserver.tld/myowner
|
||||
|
||||
# Unpublish a version
|
||||
$ composer package:unpublish gitea:myserver.tld/myowner myvendor/mypackage@1.2.3
|
||||
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- All builds are dirty right now. Cloning logic is to be implemented.
|
||||
@@ -5,6 +5,13 @@ namespace NoccyLabs\Composer\PackagePlugin\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Composer\Command\BaseCommand;
|
||||
use NoccyLabs\Composer\PackagePlugin\Package\PackageBuilder;
|
||||
use NoccyLabs\Composer\PackagePlugin\Package\PackagePublisher;
|
||||
use NoccyLabs\Composer\PackagePlugin\Project\ProjectInfo;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\Credentials\InsecureStore;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\Gitea\GiteaProvider;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\RegistryFactory;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class PackageBuildCommand extends BaseCommand
|
||||
{
|
||||
@@ -13,12 +20,51 @@ class PackageBuildCommand extends BaseCommand
|
||||
$this
|
||||
->setName('package:build')
|
||||
->setAliases([ "package" ])
|
||||
->setDescription("Package the library into a zipball, or publish directly");
|
||||
->setDescription("Package the library into a zipball, or publish directly")
|
||||
->addOption("publish", null, InputOption::VALUE_REQUIRED, "Publish to registry immediately after building")
|
||||
->addOption("dirty", null, InputOption::VALUE_NONE, "Build directly from source without cloning")
|
||||
->addOption("force", null, InputOption::VALUE_NONE, "Build even if the output file already exists")
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeln('Executing');
|
||||
$registry = $input->getOption("publish");
|
||||
|
||||
$builder = new PackageBuilder($output);
|
||||
|
||||
$project = ProjectInfo::read();
|
||||
|
||||
if ($registry && file_exists($project->filename)) {
|
||||
$output->writeln("<fg=black;bg=yellow>Package file already exists. Pass --force to rebuild it.</>");
|
||||
} else {
|
||||
$builder->build($project, $input->getOption("force"));
|
||||
}
|
||||
|
||||
if ($registry) {
|
||||
$credentials = new InsecureStore();
|
||||
|
||||
$providers = [
|
||||
'gitea' => new GiteaProvider($credentials)
|
||||
];
|
||||
|
||||
$factory = new RegistryFactory($providers);
|
||||
$publisher = new PackagePublisher($factory, $output);
|
||||
|
||||
if (!$registry) {
|
||||
$output->writeln([
|
||||
"Missing registry to publish to. Please specify the registry like this:",
|
||||
"",
|
||||
" <info>gitea:<server></> - to publish to <info>server</> as the default user",
|
||||
" <info>gitea:<server>/<owner></> - to publish to <info>server</> as <info>owner</>",
|
||||
""
|
||||
]);
|
||||
return self::INVALID;
|
||||
}
|
||||
|
||||
$publisher->publish($project, $registry);
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,13 @@ namespace NoccyLabs\Composer\PackagePlugin\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Composer\Command\BaseCommand;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\Credentials\InsecureStore;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\Gitea\GiteaProvider;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\RegistryFactory;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class PackageLoginCommand extends BaseCommand
|
||||
{
|
||||
@@ -12,12 +19,23 @@ class PackageLoginCommand extends BaseCommand
|
||||
{
|
||||
$this
|
||||
->setName('package:login')
|
||||
->setDescription("Login to a composer package registry");
|
||||
->setDescription("Login to a composer package registry")
|
||||
->addOption("forget", null, InputOption::VALUE_NONE, "Forget the login for the registry")
|
||||
->addArgument("server", InputArgument::OPTIONAL, "Registry specification")
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeln('Executing');
|
||||
$credentials = new InsecureStore();
|
||||
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$server = $input->getArgument("server");
|
||||
$user = $io->ask("Username");
|
||||
$token = $io->ask("Token");
|
||||
|
||||
$credentials->storeToken($server, $user, $token);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,12 @@ namespace NoccyLabs\Composer\PackagePlugin\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Composer\Command\BaseCommand;
|
||||
use NoccyLabs\Composer\PackagePlugin\Package\PackagePublisher;
|
||||
use NoccyLabs\Composer\PackagePlugin\Project\ProjectInfo;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\Credentials\InsecureStore;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\Gitea\GiteaProvider;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\RegistryFactory;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
class PackagePublishCommand extends BaseCommand
|
||||
{
|
||||
@@ -12,12 +18,38 @@ class PackagePublishCommand extends BaseCommand
|
||||
{
|
||||
$this
|
||||
->setName('package:publish')
|
||||
->setDescription("Publish a package to a composer repository");
|
||||
->setDescription("Publish a package to a composer package registry")
|
||||
->addArgument("registry", InputArgument::OPTIONAL, "The registry to publish to")
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeln('Executing');
|
||||
$credentials = new InsecureStore();
|
||||
|
||||
$providers = [
|
||||
'gitea' => new GiteaProvider($credentials)
|
||||
];
|
||||
|
||||
$registry = new RegistryFactory($providers);
|
||||
$publisher = new PackagePublisher($registry, $output);
|
||||
|
||||
$project = ProjectInfo::read();
|
||||
|
||||
$registry = $input->getArgument("registry");
|
||||
if (!$registry) {
|
||||
$output->writeln([
|
||||
"Missing registry to publish to. Please specify the registry like this:",
|
||||
"",
|
||||
" <info>gitea:<server></> - to publish to <info>server</> as the default user",
|
||||
" <info>gitea:<server>/<owner></> - to publish to <info>server</> as <info>owner</>",
|
||||
""
|
||||
]);
|
||||
return self::INVALID;
|
||||
}
|
||||
|
||||
$publisher->publish($project, $registry);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,13 @@ namespace NoccyLabs\Composer\PackagePlugin\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Composer\Command\BaseCommand;
|
||||
use NoccyLabs\Composer\PackagePlugin\Package\PackagePublisher;
|
||||
use NoccyLabs\Composer\PackagePlugin\Project\ProjectInfo;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\Credentials\InsecureStore;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\Gitea\GiteaProvider;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\RegistryFactory;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class PackageUnpublishCommand extends BaseCommand
|
||||
{
|
||||
@@ -12,13 +19,44 @@ class PackageUnpublishCommand extends BaseCommand
|
||||
{
|
||||
$this
|
||||
->setName('package:unpublish')
|
||||
->setDescription("Unpublish a version of a package or a package from a composer repository");
|
||||
->setDescription("Unpublish a version of a package or a package from a composer package registry")
|
||||
->addOption("all", null, InputOption::VALUE_NONE, "Allow unpublish all versions of a package by passing all as the version")
|
||||
->addArgument("registry", InputArgument::REQUIRED, "The registry to unpublish from")
|
||||
->addArgument("version", InputArgument::REQUIRED, "The version to unpublish (or with --all, 'all' for all)")
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeln('Executing');
|
||||
$credentials = new InsecureStore();
|
||||
|
||||
return 0;
|
||||
$providers = [
|
||||
'gitea' => new GiteaProvider($credentials)
|
||||
];
|
||||
|
||||
$factory = new RegistryFactory($providers);
|
||||
$publisher = new PackagePublisher($factory, $output);
|
||||
|
||||
$project = ProjectInfo::read();
|
||||
|
||||
$registry = $input->getArgument("registry");
|
||||
if (!$registry) {
|
||||
$output->writeln([
|
||||
"Missing registry to unpublish from. Please specify the registry like this:",
|
||||
"",
|
||||
" <info>gitea:<server></> - to unpublish from <info>server</> for the default user",
|
||||
" <info>gitea:<server>/<owner></> - to unpublish from <info>server</> for <info>owner</>",
|
||||
""
|
||||
]);
|
||||
return self::INVALID;
|
||||
}
|
||||
|
||||
if ($pickVersion = $input->getArgument("version")) {
|
||||
$project->version = $pickVersion;
|
||||
}
|
||||
|
||||
$publisher->unpublish($project, $registry);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class CommandProvider implements CommandProviderCapability
|
||||
new Command\PackageBuildCommand(),
|
||||
new Command\PackageLoginCommand(),
|
||||
new Command\PackagePublishCommand(),
|
||||
new Command\PackageUnpublishCommand(),
|
||||
// new Command\PackageUnpublishCommand(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
109
src/Package/PackageBuilder.php
Normal file
109
src/Package/PackageBuilder.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Composer\PackagePlugin\Package;
|
||||
|
||||
use NoccyLabs\Composer\PackagePlugin\Project\ProjectInfo;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ZipArchive;
|
||||
|
||||
class PackageBuilder
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
private readonly OutputInterface $output,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function build(ProjectInfo $project, bool $overwrite = false): void
|
||||
{
|
||||
$packageName = $project->name;
|
||||
$packageVersion = $project->version;
|
||||
if (!$packageVersion) {
|
||||
exec("git describe --tags 2>/dev/null", $tags, $ret);
|
||||
if ($ret != 0) {
|
||||
throw new \Exception("Non-zero exitcode from git describe --tags, have you created a tag?");
|
||||
}
|
||||
$packageVersion = trim(reset($tags));
|
||||
}
|
||||
$this->output->writeln("Creating <info>{$project->filename}</> from <options=bold>{$packageName}</>@<options=bold>{$packageVersion}</>...");
|
||||
$filename = getcwd() . "/" . $project->filename;
|
||||
|
||||
if (file_exists($filename) && !$overwrite) {
|
||||
throw new \Exception("The package file already exists, pass --force to overwrite it.");
|
||||
}
|
||||
|
||||
// TODO clone repo before build unless dirty
|
||||
|
||||
$files = $this->listFiles();
|
||||
$total = array_sum(array_map(filesize(...), $files));
|
||||
$unit = "B"; $units = [ "KiB", "MiB" ];
|
||||
while ($total > 1024 && count($units)) {
|
||||
$total /= 1024;
|
||||
$unit = array_shift($units);
|
||||
}
|
||||
$size = sprintf("%.1f%s", $total, $unit);
|
||||
|
||||
$this->output->writeln("Adding <options=bold>".count($files)."</> files (<options=bold>{$size}</>)");
|
||||
|
||||
if (extension_loaded("zip")) {
|
||||
$progress = new ProgressBar($this->output, count($files));
|
||||
$this->packWithExtension($filename, $files, $packageVersion, $progress);
|
||||
$this->output->writeln("");
|
||||
} else {
|
||||
$this->packWithCommand($filename, $files);
|
||||
}
|
||||
}
|
||||
|
||||
private function listFiles(): array
|
||||
{
|
||||
exec("git ls-files", $out, $ret);
|
||||
if ($ret != 0) {
|
||||
throw new \Exception("Non-zero exitcode from git ls-files");
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function packWithExtension(string $zipFile, array $files, string $version, ProgressBar $progress): void
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($zipFile, ZipArchive::CREATE);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($file === 'composer.json') {
|
||||
$json = json_decode(file_get_contents($file));
|
||||
$json->version = $version;
|
||||
$json = json_encode($json, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT);
|
||||
$zip->addFromString($file, $json);
|
||||
} else {
|
||||
$zip->addFile($file);
|
||||
}
|
||||
$progress->advance();
|
||||
}
|
||||
$zip->close();
|
||||
$progress->finish();
|
||||
}
|
||||
|
||||
private function packWithCommand(string $zipFile, array $files): void
|
||||
{
|
||||
if ($zipCommand = exec("which zip")) {
|
||||
// TODO
|
||||
$cmd = $zipCommand;
|
||||
$args = [ "a", $zipFile, ...$files ];
|
||||
} elseif ($szCommand = exec("which 7z")) {
|
||||
// TODO
|
||||
$cmd = $szCommand;
|
||||
$args = [ "a", "-tzip", $zipFile, ...$files ];
|
||||
} else {
|
||||
throw new \Exception("Unable to find zip or 7z tool, and the zip extension is not loaded");
|
||||
}
|
||||
$cmdl = sprintf("%s %s", escapeshellcmd($cmd), join(" ", array_map(escapeshellarg(...), $args)));
|
||||
exec($cmdl, $out, $ret);
|
||||
if ($ret !== 0) {
|
||||
$timmed = (mb_strlen($cmdl) > 32) ? (mb_substr($cmdl, 0, 32) . "...") : $cmdl;
|
||||
throw new \Exception("Non-zero exit code from command: {$timmed}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
35
src/Package/PackagePublisher.php
Normal file
35
src/Package/PackagePublisher.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Composer\PackagePlugin\Package;
|
||||
|
||||
use NoccyLabs\Composer\PackagePlugin\Project\ProjectInfo;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\RegistryFactory;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class PackagePublisher
|
||||
{
|
||||
public function __construct(
|
||||
public readonly RegistryFactory $registryFactory,
|
||||
public readonly OutputInterface $output,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function publish(ProjectInfo $project, string $registryUri): void
|
||||
{
|
||||
// $this->output->writeln("Resolving registry for <comment>{$registryUri}</>...");
|
||||
$registry = $this->registryFactory->createRegistryFromUri($registryUri);
|
||||
|
||||
$this->output->writeln("Publishing package file <info>{$project->filename}</> to <comment>{$registry->server}</> as <comment>{$registry->owner}</>...");
|
||||
$registry->publishPackageVersion($project);
|
||||
}
|
||||
|
||||
public function unpublish(ProjectInfo $project, string $registryUri): void
|
||||
{
|
||||
// $this->output->writeln("Resolving registry for <comment>{$registryUri}</>...");
|
||||
$registry = $this->registryFactory->createRegistryFromUri($registryUri);
|
||||
|
||||
$this->output->writeln("Unpublishing package <options=bold>{$project->name}</>@<options=bold>{$project->version}</> from <comment>{$registry->server}</> for <comment>{$registry->owner}</>...");
|
||||
$registry->unpublishPackageVersion($project);
|
||||
}
|
||||
}
|
||||
53
src/Project/ProjectInfo.php
Normal file
53
src/Project/ProjectInfo.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Composer\PackagePlugin\Project;
|
||||
|
||||
class ProjectInfo
|
||||
{
|
||||
private static ?ProjectInfo $project = null;
|
||||
|
||||
public static function read(): ProjectInfo
|
||||
{
|
||||
return self::$project
|
||||
? self::$project
|
||||
: (self::$project = self::readProject());
|
||||
}
|
||||
|
||||
private static function readProject(?string $path = null): ProjectInfo
|
||||
{
|
||||
$path ??= getcwd();
|
||||
|
||||
$json = file_get_contents($path."/composer.json");
|
||||
$manifest = json_decode($json);
|
||||
|
||||
if (file_exists($path."/.git")) {
|
||||
$version = trim(exec("git describe --tags 2>/dev/null")) ?: null;
|
||||
}
|
||||
if (!$version) {
|
||||
$version = $manifest->version ?? "1.0.0";
|
||||
}
|
||||
|
||||
$packageName = $manifest->name;
|
||||
|
||||
$filename = sprintf("%s--%s.zip", strtr($packageName, [ "/" => "-" ]), $version);
|
||||
|
||||
|
||||
return new ProjectInfo(
|
||||
path: $path,
|
||||
name: $packageName,
|
||||
version: $version,
|
||||
filename: $filename,
|
||||
manifest: $manifest,
|
||||
);
|
||||
}
|
||||
|
||||
public function __construct(
|
||||
public readonly string $path,
|
||||
public readonly string $name,
|
||||
public ?string $version,
|
||||
public readonly string $filename,
|
||||
public readonly object $manifest,
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
77
src/Registry/Credentials/InsecureStore.php
Normal file
77
src/Registry/Credentials/InsecureStore.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Composer\PackagePlugin\Registry\Credentials;
|
||||
|
||||
|
||||
|
||||
class InsecureStore implements StoreInterface
|
||||
{
|
||||
private string $filename;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->filename = getenv("HOME")."/.config/composer/package-plugin.json";
|
||||
}
|
||||
|
||||
private function readConfig(): ?object
|
||||
{
|
||||
if (!file_exists($this->filename)) return null;
|
||||
$json = file_get_contents($this->filename);
|
||||
return json_decode($json);
|
||||
}
|
||||
|
||||
private function writeConfig(object $config): void
|
||||
{
|
||||
$json = json_encode($config, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
file_put_contents($this->filename, $json);
|
||||
}
|
||||
|
||||
public function getToken(string $server, ?string $owner): string
|
||||
{
|
||||
$conf = $this->readConfig();
|
||||
if (!$conf) {
|
||||
throw new \Exception("No credentials found for {$server}");
|
||||
}
|
||||
$credentials = (array)$conf->credentials;
|
||||
if (array_key_exists($server, $credentials)) {
|
||||
$serverCredentials = (array)$credentials[$server];
|
||||
if ($owner && array_key_exists($owner, $serverCredentials)) {
|
||||
$token = $serverCredentials[$owner]->token;
|
||||
$user = $owner;
|
||||
} else {
|
||||
$keys = array_keys($serverCredentials);
|
||||
$user = reset($keys);
|
||||
$first = reset($serverCredentials);
|
||||
$token = $first->token;
|
||||
}
|
||||
return sprintf("%s:%s", $user, $token);
|
||||
}
|
||||
|
||||
throw new \Exception("No credentials found for {$server}");
|
||||
}
|
||||
|
||||
public function storeToken(string $server, string $user, string $token): void
|
||||
{
|
||||
$conf = $this->readConfig();
|
||||
if (!$conf) {
|
||||
$conf = (object)[];
|
||||
}
|
||||
if (!isset($conf->credentials)) {
|
||||
$conf->credentials = (object)[];
|
||||
}
|
||||
if (!isset($conf->credentials->{$server})) {
|
||||
$conf->credentials->{$server} = (object)[
|
||||
$user => [
|
||||
'user' => $user,
|
||||
'token' => $token
|
||||
]
|
||||
];
|
||||
} else {
|
||||
$conf->credentials->{$server}->{$user} = (object)[
|
||||
'user' => $user,
|
||||
'token' => $token
|
||||
];
|
||||
}
|
||||
$this->writeConfig($conf);
|
||||
}
|
||||
}
|
||||
8
src/Registry/Credentials/StoreInterface.php
Normal file
8
src/Registry/Credentials/StoreInterface.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Composer\PackagePlugin\Registry\Credentials;
|
||||
|
||||
interface StoreInterface
|
||||
{
|
||||
public function getToken(string $server, ?string $owner): string;
|
||||
}
|
||||
21
src/Registry/Gitea/GiteaProvider.php
Normal file
21
src/Registry/Gitea/GiteaProvider.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Composer\PackagePlugin\Registry\Gitea;
|
||||
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\ProviderTrait;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\RegistryInterface;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\RegistryProviderInterface;
|
||||
|
||||
class GiteaProvider implements RegistryProviderInterface
|
||||
{
|
||||
use ProviderTrait;
|
||||
|
||||
public function createRegistry(array $params): RegistryInterface
|
||||
{
|
||||
$server = array_shift($params);
|
||||
$owner = array_shift($params);
|
||||
$token = $this->credentials->getToken($server, $owner);
|
||||
[$user, $_] = explode(":", $token, 2);
|
||||
return new GiteaRegistry($server, $owner??$user, $token);
|
||||
}
|
||||
}
|
||||
113
src/Registry/Gitea/GiteaRegistry.php
Normal file
113
src/Registry/Gitea/GiteaRegistry.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Composer\PackagePlugin\Registry\Gitea;
|
||||
|
||||
use NoccyLabs\Composer\PackagePlugin\Project\ProjectInfo;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\RegistryInterface;
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\RegistryTrait;
|
||||
|
||||
class GiteaRegistry implements RegistryInterface
|
||||
{
|
||||
use RegistryTrait;
|
||||
|
||||
public function publishPackageVersion(ProjectInfo $project): void
|
||||
{
|
||||
$url = sprintf("https://%s/api/packages/%s/composer?version=%s", $this->server, $this->owner, $project->version);
|
||||
$request = [
|
||||
'method' => 'PUT',
|
||||
'url' => $url,
|
||||
'auth' => $this->token,
|
||||
'filename' => $project->filename,
|
||||
];
|
||||
$this->invoke($request);
|
||||
}
|
||||
|
||||
public function unpublishPackageVersion(ProjectInfo $project): void
|
||||
{
|
||||
$url = sprintf("https://%s/api/packages/%s/composer?version=%s", $this->server, $this->owner, $project->version);
|
||||
$request = [
|
||||
'method' => 'PUT',
|
||||
'url' => $url,
|
||||
'auth' => $this->token,
|
||||
'filename' => $project->filename,
|
||||
];
|
||||
$this->invoke($request);
|
||||
|
||||
}
|
||||
|
||||
public function unpublishPackage(ProjectInfo $project): void
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private function invoke(array $request): mixed
|
||||
{
|
||||
if (extension_loaded("curl")) {
|
||||
return $this->invokeExt($request);
|
||||
}
|
||||
return $this->invokeCurl($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke using the curl php extension
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function invokeExt(array $request): mixed
|
||||
{
|
||||
$fd = fopen($request['filename'], "rb");
|
||||
$fdlen = filesize($request['filename']);
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $request['url']);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
if ($request['method'] === 'PUT') {
|
||||
curl_setopt($curl, CURLOPT_PUT, 1);
|
||||
curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
|
||||
curl_setopt($curl, CURLOPT_USERPWD, $this->token);
|
||||
curl_setopt($curl, CURLOPT_INFILE, $fd);
|
||||
curl_setopt($curl, CURLOPT_INFILESIZE, $fdlen);
|
||||
}
|
||||
$response = curl_exec($curl);
|
||||
$code = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
|
||||
fclose($fd);
|
||||
|
||||
switch ($code) {
|
||||
case 201:
|
||||
return true;
|
||||
case 400:
|
||||
throw new \Exception("Bad request");
|
||||
case 409:
|
||||
throw new \Exception("Package with this version already exists");
|
||||
default:
|
||||
if ($code < 200 && $code >= 300)
|
||||
throw new \Exception("Request failed: {$code}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke using the curl cli utility
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function invokeCurl(array $request): mixed
|
||||
{
|
||||
|
||||
$cmd = trim(exec("which curl"));
|
||||
$args = [ "--user", $this->token ];
|
||||
|
||||
if ($request['method'] === 'PUT') {
|
||||
$args[] = '--upload-file';
|
||||
$args[] = $request['filename'];
|
||||
}
|
||||
|
||||
$args[] = $request['url'];
|
||||
|
||||
$cmdl = escapeshellcmd($cmd)." ".join(" ", array_map(escapeshellarg(...), $args));
|
||||
passthru($cmdl);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
15
src/Registry/ProviderTrait.php
Normal file
15
src/Registry/ProviderTrait.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Composer\PackagePlugin\Registry;
|
||||
|
||||
use NoccyLabs\Composer\PackagePlugin\Registry\Credentials\StoreInterface;
|
||||
|
||||
trait ProviderTrait
|
||||
{
|
||||
public function __construct(
|
||||
public readonly StoreInterface $credentials,
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
34
src/Registry/RegistryFactory.php
Normal file
34
src/Registry/RegistryFactory.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Composer\PackagePlugin\Registry;
|
||||
|
||||
|
||||
class RegistryFactory
|
||||
{
|
||||
private array $providers = [];
|
||||
|
||||
public function __construct(
|
||||
array $providers = [],
|
||||
)
|
||||
{
|
||||
foreach ($providers as $prefix=>$provider) {
|
||||
$this->registerProvider($prefix, $provider);
|
||||
}
|
||||
}
|
||||
|
||||
public function registerProvider(string $prefix, RegistryProviderInterface $provider): void
|
||||
{
|
||||
$this->providers[$prefix] = $provider;
|
||||
}
|
||||
|
||||
public function createRegistryFromUri(string $uri): RegistryInterface
|
||||
{
|
||||
$params = explode(":", $uri);
|
||||
$type = array_shift($params);
|
||||
if (!isset($this->providers[$type])) {
|
||||
throw new \Exception("Invalid registry provider type '{$type}'. Supported are ".join(", ",array_keys($this->providers)));
|
||||
}
|
||||
$registry = $this->providers[$type]->createRegistry($params);
|
||||
return $registry;
|
||||
}
|
||||
}
|
||||
13
src/Registry/RegistryInterface.php
Normal file
13
src/Registry/RegistryInterface.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Composer\PackagePlugin\Registry;
|
||||
|
||||
use NoccyLabs\Composer\PackagePlugin\Project\ProjectInfo;
|
||||
|
||||
interface RegistryInterface
|
||||
{
|
||||
public function publishPackageVersion(ProjectInfo $project): void;
|
||||
public function unpublishPackageVersion(ProjectInfo $project): void;
|
||||
public function unpublishPackage(ProjectInfo $project): void;
|
||||
|
||||
}
|
||||
8
src/Registry/RegistryProviderInterface.php
Normal file
8
src/Registry/RegistryProviderInterface.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Composer\PackagePlugin\Registry;
|
||||
|
||||
interface RegistryProviderInterface
|
||||
{
|
||||
public function createRegistry(array $params): RegistryInterface;
|
||||
}
|
||||
15
src/Registry/RegistryTrait.php
Normal file
15
src/Registry/RegistryTrait.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Composer\PackagePlugin\Registry;
|
||||
|
||||
trait RegistryTrait
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $server,
|
||||
public readonly ?string $owner,
|
||||
public readonly string $token,
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user