diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php
index c405d9e5..7345c055 100644
--- a/src/Composer/Repository/ArtifactRepository.php
+++ b/src/Composer/Repository/ArtifactRepository.php
@@ -31,7 +31,11 @@ class ArtifactRepository extends ArrayRepository implements PacketonRepositoryIn
protected $lookup;
/** @var array */
- protected $archives;
+ protected array $archives = [];
+
+ protected array $archivesOverwriteInfo = [];
+
+ protected array $archivesOverwriteInfoByRef = [];
public function __construct(
protected array $repoConfig,
@@ -51,6 +55,7 @@ public function __construct(
$this->lookup = $repoConfig['url'] ?? null;
$this->lookup = $this->lookup === '_unset' ? null : $this->lookup;
$this->archives = $this->repoConfig['archives'] ?? [];
+ $this->archivesOverwriteInfo = $this->repoConfig['archive_overwrite_mapping'] ?? [];
$this->process ??= new ProcessExecutor($this->io);
}
@@ -136,7 +141,7 @@ public function allArtifacts(): iterable
private function doInitialize(): void
{
foreach ($this->allArtifacts() as $ref => $file) {
- $package = $this->getComposerInformation($file, $ref);
+ $package = $this->getComposerInformation($file, $ref, $this->archivesOverwriteInfoByRef[$ref] ?? null);
if (!$package) {
$this->io->writeError("File {$file->getBasename()} doesn't seem to hold a package", true, IOInterface::VERBOSE);
continue;
@@ -164,6 +169,8 @@ private function scanArchives(array $archives): iterable
continue;
}
+ $this->archivesOverwriteInfoByRef[$zip->getReference()] = $this->archivesOverwriteInfo[$zip->getId()] ?? null;
+
yield $zip->getReference() => new \SplFileInfo($path);
}
}
@@ -190,7 +197,7 @@ private function scanDirectory(string $path): iterable
/**
* {@inheritdoc}
*/
- public function getComposerInformation(\SplFileInfo $file, $ref = null): ?BasePackage
+ public function getComposerInformation(\SplFileInfo $file, mixed $ref = null, ?array $overwrite = null): ?BasePackage
{
$json = null;
$fileExtension = pathinfo($file->getPathname(), PATHINFO_EXTENSION);
@@ -217,6 +224,8 @@ public function getComposerInformation(\SplFileInfo $file, $ref = null): ?BasePa
}
$package = JsonFile::parseJson($json, $file->getPathname().'#composer.json');
+ $package = array_merge($package, $overwrite ?? []);
+
$package['dist'] = [
'type' => $fileType,
'url' => strtr($file->getPathname(), '\\', '/'),
diff --git a/src/Controller/PackageController.php b/src/Controller/PackageController.php
index 5bb6074b..7c97f240 100644
--- a/src/Controller/PackageController.php
+++ b/src/Controller/PackageController.php
@@ -598,12 +598,8 @@ public function updatePackageAction(Request $req, $name): Response
$doctrine = $this->registry;
$this->checkSubrepositoryAccess($name);
- try {
- /** @var Package $package */
- $package = $doctrine
- ->getRepository(Package::class)
- ->getPackageByName($name);
- } catch (NoResultException) {
+ $package = $doctrine->getRepository(Package::class)->getPackageByName($name);
+ if (null === $package) {
return new JsonResponse(['status' => 'error', 'message' => 'Package not found'], 404);
}
diff --git a/src/Controller/PushPackagesController.php b/src/Controller/PushPackagesController.php
index 424a0dea..15c6fc66 100644
--- a/src/Controller/PushPackagesController.php
+++ b/src/Controller/PushPackagesController.php
@@ -4,6 +4,7 @@
namespace Packeton\Controller;
+use Packeton\Exception\ValidationException;
use Packeton\Form\Handler\PushPackageHandler;
use Packeton\Form\Type\Push\NexusPushType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -17,13 +18,18 @@
#[Route(defaults: ['_format' => 'json'])]
class PushPackagesController extends AbstractController
{
- #[Route('/packages/upload/{name}/{version}', name: 'package_push_nexus', requirements: ['name' => '%package_name_regex%'], methods: ['PUT', 'POST'])]
#[IsGranted('ROLE_MAINTAINER')]
+ #[Route('/packages/upload/{name}/{version}', name: 'package_push_nexus', requirements: ['name' => '%package_name_regex%'], methods: ['PUT', 'POST'])]
+ #[Route('/api/packages/upload/{name}/{version}', name: 'package_push_api', requirements: ['name' => '%package_name_regex%'], methods: ['PUT'])]
public function pushNexusAction(PushPackageHandler $handler, Request $request, string $name, string $version): Response
{
$form = $this->createApiForm(NexusPushType::class, options: ['method' => $request->getMethod()]);
- $handler($form, $request, $name, $version);
+ try {
+ $handler($form, $request, $name, $version, $this->getUser());
+ } catch (ValidationException $e) {
+ return new JsonResponse(['title' => $e->getMessage(), 'errors' => $e->getFormErrors()], 400);
+ }
return new JsonResponse([], 201);
}
diff --git a/src/Controller/ZipballController.php b/src/Controller/ZipballController.php
index 8e597115..fc8a5e91 100644
--- a/src/Controller/ZipballController.php
+++ b/src/Controller/ZipballController.php
@@ -9,6 +9,7 @@
use Packeton\Entity\Package;
use Packeton\Entity\Zipball;
use Packeton\Event\ZipballEvent;
+use Packeton\Exception\ZipballException;
use Packeton\Model\UploadZipballStorage;
use Packeton\Package\RepTypes;
use Packeton\Service\DistManager;
@@ -40,7 +41,7 @@ public function __construct(
) {
}
- #[Route('/archive/upload', name: 'archive_upload', methods: ["POST"])]
+ #[Route('/archive/upload', name: 'archive_upload', methods: ["POST"], format: 'json')]
#[IsGranted('ROLE_MAINTAINER')]
public function upload(Request $request): Response
{
@@ -52,8 +53,13 @@ public function upload(Request $request): Response
return new JsonResponse(['error' => 'File is empty'], 400);
}
- $result = $this->storage->save($file);
- return new JsonResponse($result, $result['code'] ?? 201);
+ try {
+ $result = $this->storage->save($file);
+ } catch (ZipballException $e) {
+ return new JsonResponse(['error' => $e->getMessage()], max($e->getCode(), 300));
+ }
+
+ return new JsonResponse(['id' => $result->getId(), 'filename' => $result->getFilename(), 'size' => $result->getFileSize()], 201);
}
#[Route('/archive/remove/{id}', name: 'archive_remove', methods: ["DELETE"])]
diff --git a/src/Entity/Package.php b/src/Entity/Package.php
index 482a6758..c9607777 100644
--- a/src/Entity/Package.php
+++ b/src/Entity/Package.php
@@ -455,6 +455,7 @@ public function getRepoConfig(): array
'repoType' => $this->repoType ?: 'vcs',
'subDirectory' => $this->getSubDirectory(),
'archives' => $this->getArchives(),
+ 'archive_overwrite_mapping' => $this->getArchiveOverwrite(),
'oauth2' => $this->integration,
'externalRef' => $this->externalRef,
'customVersions' => $this->getCustomVersions(),
diff --git a/src/Entity/PackageSerializedTrait.php b/src/Entity/PackageSerializedTrait.php
index a22145cd..bbe0df37 100644
--- a/src/Entity/PackageSerializedTrait.php
+++ b/src/Entity/PackageSerializedTrait.php
@@ -6,6 +6,7 @@
use Composer\Package\Version\VersionParser;
use Composer\Semver\Constraint\Constraint;
+use Packeton\Util\PacketonUtils;
trait PackageSerializedTrait
{
@@ -76,7 +77,7 @@ public function getArchives(): ?array
return $this->serializedFields['archives'] ?? null;
}
- public function getAllArchives(): ?array
+ public function getAllArchives(): array
{
$archives = $this->serializedFields['archives'] ?? [];
foreach ($this->getCustomVersions() as $version) {
@@ -102,6 +103,38 @@ public function setArchives(?array $archives): void
$this->setSerialized('archives', $archives);
}
+ public function setArchiveOverwrite(int $archiveId, array $versionData): void
+ {
+ $mapping = $this->getSerialized('archive_version_mapping', 'array') ?? [];
+ $archives = $this->getArchives();
+ $version = $versionData['version'] ?? null;
+
+ $unset = [];
+ foreach ($mapping as $id => $data) {
+ if (($data['version'] ?? '_na') === $version) {
+ $unset[] = $id;
+ }
+ }
+
+ $archives[] = $archiveId;
+ $archives = array_diff($archives, $unset);
+ $this->setArchives(array_values(array_unique($archives)));
+
+ $mapping[$archiveId] = $versionData;
+ foreach ($mapping as $id => $data) {
+ if (!in_array($id, $archives)) {
+ unset($mapping[$id]);
+ }
+ }
+
+ $this->setSerialized('archive_version_mapping', $mapping);
+ }
+
+ public function getArchiveOverwrite(): array
+ {
+ return $this->getSerialized('archive_version_mapping', 'array') ?? [];
+ }
+
public function setUpdateFlags(int $flags): void
{
$this->setSerialized('update_flags', $flags);
@@ -199,6 +232,15 @@ public function setCustomVersions($versions): void
$this->setSerialized('custom_versions', $versions);
}
+ public function addCustomVersions(array $versionData): void
+ {
+ $versions = $this->getCustomVersions();
+ $versions = PacketonUtils::buildChoices($versions, 'version');
+ $versions[$versionData['version']] = $versionData;
+
+ $this->setCustomVersions(array_values($versions));
+ }
+
public function getCustomComposerJson(): array
{
return $this->getSerialized('custom_composer_json', 'array', []);
diff --git a/src/Exception/ValidationException.php b/src/Exception/ValidationException.php
new file mode 100644
index 00000000..8bafefa0
--- /dev/null
+++ b/src/Exception/ValidationException.php
@@ -0,0 +1,44 @@
+errors = $errors instanceof FormInterface ? $exception->getErrors($errors) : $errors;
+
+ return $exception;
+ }
+
+ private function getErrors(FormInterface $form): array
+ {
+ $errors = $base = [];
+
+ foreach ($form->getErrors() as $error) {
+ $base[] = $error->getMessage();
+ }
+ foreach ($form as $child) {
+ foreach ($child->getErrors(true) as $error) {
+ $errors[$child->getName()] = $error->getMessage();
+ }
+ }
+ if (count($base) > 0) {
+ $errors['root'] = implode("\n", $base);
+ }
+
+ return $errors;
+ }
+
+ public function getFormErrors(): array
+ {
+ return $this->errors;
+ }
+}
diff --git a/src/Exception/ZipballException.php b/src/Exception/ZipballException.php
new file mode 100644
index 00000000..94101bb0
--- /dev/null
+++ b/src/Exception/ZipballException.php
@@ -0,0 +1,9 @@
+request->set('version', $version ?? 'dev-master');
+
$form->handleRequest($request);
if (!$form->isSubmitted() || !$form->isValid()) {
- throw new \RuntimeException('todo');
+ throw ValidationException::create("Validation errors", $form);
}
- /** @var PushRequestDtoInterface $artifact */
- $artifact = $form->getData();
+ /** @var PushRequestDtoInterface $dtoRequest */
+ $dtoRequest = $form->getData();
$package = $this->getRepo()->getPackageByName($name);
if (null === $package) {
$package = $this->createArtifactPackage($name, $user);
}
-
+ try {
+ $this->handler->addVersion($dtoRequest, $package);
+ } catch (ZipballException $e) {
+ throw ValidationException::create($e->getMessage(), previous: $e);
+ }
}
private function getRepo(): PackageRepository
@@ -53,7 +60,12 @@ private function createArtifactPackage(string $name, ?UserInterface $user = null
$em = $this->registry->getManager();
$package = new Package();
$package->setName($name);
- $package->setRepoType(RepTypes::ARTIFACT);
+ $package->setRepoType(RepTypes::CUSTOM);
+ $package->setRepositoryPath(null);
+
+ $user = null !== $user ?
+ $this->registry->getRepository(User::class)->findOneBy(['username' => $user->getUserIdentifier()])
+ : $user;
if ($user instanceof User) {
$package->addMaintainer($user);
diff --git a/src/Form/Model/NexusPushRequestDto.php b/src/Form/Model/NexusPushRequestDto.php
index c3c3df0a..73e2c953 100644
--- a/src/Form/Model/NexusPushRequestDto.php
+++ b/src/Form/Model/NexusPushRequestDto.php
@@ -35,4 +35,17 @@ public function getPackageVersion(): string
{
return $this->version;
}
+
+ public function getSource(): ?array
+ {
+ if ($this->srcRef !== null && $this->srcUrl !== null) {
+ return [
+ 'type' => $this->srcType ?? 'git',
+ 'url' => $this->srcUrl,
+ 'reference' => $this->srcRef,
+ ];
+ }
+
+ return null;
+ }
}
diff --git a/src/Form/Model/PushRequestDtoInterface.php b/src/Form/Model/PushRequestDtoInterface.php
index 662b66e5..39fdc05f 100644
--- a/src/Form/Model/PushRequestDtoInterface.php
+++ b/src/Form/Model/PushRequestDtoInterface.php
@@ -13,4 +13,6 @@ public function getArtifact(): File;
public function getPackageName(): string;
public function getPackageVersion(): string;
+
+ public function getSource(): ?array;
}
diff --git a/src/Form/RequestHandler/PutRequestHandler.php b/src/Form/RequestHandler/PutRequestHandler.php
index 64b82254..0abcdca6 100644
--- a/src/Form/RequestHandler/PutRequestHandler.php
+++ b/src/Form/RequestHandler/PutRequestHandler.php
@@ -8,13 +8,15 @@
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\RequestHandlerInterface;
use Symfony\Component\HttpFoundation\File\File;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
#[Exclude]
class PutRequestHandler implements RequestHandlerInterface
{
public function __construct(
- private readonly RequestHandlerInterface $requestHandle
+ private readonly RequestHandlerInterface $requestHandle,
+ private readonly string $singleFieldName = 'file',
) {
}
@@ -31,13 +33,25 @@ public function isFileUpload(mixed $data): bool
return $data instanceof File;
}
- protected function decodeMultiPartFormData(Request $request): void
+ private function decodeMultiPartFormData(Request $request): void
{
$rawData = $request->getContent();
if (!$rawData) {
return;
}
+ $contentType = $request->headers->get('Content-Type');
+ if (null === $contentType || !str_contains($contentType, 'multipart/form-data')) {
+ $this->createUploadedFile(
+ request: $request,
+ content: $rawData,
+ fileName: (string)time(),
+ fieldName: $this->singleFieldName,
+ contentType: $contentType
+ );
+ return;
+ }
+
$boundary = substr($rawData, 0, strpos($rawData, "\r\n"));
$parts = array_slice(explode($boundary, $rawData), 1);
foreach ($parts as $part) {
@@ -66,23 +80,35 @@ protected function decodeMultiPartFormData(Request $request): void
if ($fileName === null) {
$request->request->set($fieldName, $content);
} else {
- $localFileName = tempnam(sys_get_temp_dir(), 'sfy');
- file_put_contents($localFileName, $content);
-
- $file = [
- 'name' => $fileName,
- 'type' => $headers['content-type'],
- 'tmp_name' => $localFileName,
- 'error' => 0,
- 'size' => filesize($localFileName)
- ];
-
- register_shutdown_function(static function () use ($localFileName) {
- @unlink($localFileName);
- });
-
- $request->files->set($fieldName, $file);
+ $this->createUploadedFile(
+ request: $request,
+ content: $content,
+ fileName: $fileName,
+ fieldName: $fieldName,
+ contentType: $headers['content-type'] ?? null,
+ );
}
}
}
+
+ private function createUploadedFile(Request $request, string $content, string $fileName, string $fieldName, ?string $contentType): void
+ {
+ $localFileName = tempnam(sys_get_temp_dir(), 'sfy');
+ file_put_contents($localFileName, $content);
+ $contentType ??= 'binary/octet-stream';
+
+ register_shutdown_function(static function () use ($localFileName) {
+ @unlink($localFileName);
+ });
+
+ $file = new UploadedFile(
+ path: $localFileName,
+ originalName: $fileName,
+ mimeType: $contentType,
+ error: 0,
+ test: true
+ );
+
+ $request->files->set($fieldName, $file);
+ }
}
diff --git a/src/Form/Type/Push/NexusPushType.php b/src/Form/Type/Push/NexusPushType.php
index 796ce37a..c6e3bee0 100644
--- a/src/Form/Type/Push/NexusPushType.php
+++ b/src/Form/Type/Push/NexusPushType.php
@@ -20,10 +20,11 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
->add('src-type', TextType::class, ['property_path' => 'srcType'])
->add('src-url', TextType::class, ['property_path' => 'srcUrl'])
->add('src-ref', TextType::class, ['property_path' => 'srcRef'])
- ->add('package', FileType::class);
+ ->add('package', FileType::class)
+ ->add('version', TextType::class);
$requestHandler = $builder->getRequestHandler();
- $builder->setRequestHandler(new PutRequestHandler($requestHandler));
+ $builder->setRequestHandler(new PutRequestHandler($requestHandler, 'package'));
}
public function configureOptions(OptionsResolver $resolver): void
diff --git a/src/Model/UploadZipballStorage.php b/src/Model/UploadZipballStorage.php
index 199fc598..e0080270 100644
--- a/src/Model/UploadZipballStorage.php
+++ b/src/Model/UploadZipballStorage.php
@@ -7,6 +7,7 @@
use Doctrine\Persistence\ManagerRegistry;
use League\Flysystem\FilesystemOperator;
use Packeton\Entity\Zipball;
+use Packeton\Exception\ZipballException;
use Packeton\Util\PacketonUtils;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\File\File;
@@ -67,7 +68,7 @@ public function moveToLocal(Zipball|string $zipOrReference): ?string
return $localName;
}
- public function save(File $file): array
+ public function save(File $file): Zipball
{
$mime = $file->getMimeType();
$extension = $this->guessExtension($file, $mime);
@@ -76,7 +77,7 @@ public function save(File $file): array
// Limited by ArtifactRepository, mimetype will check later, not necessary a strict validation.
if (!in_array($extension, $this->supportTypes, true)) {
$supportTypes = json_encode($this->supportTypes);
- return ['code' => 400, 'error' => "Allowed only $supportTypes archives, but given *.$extension"];
+ throw new ZipballException("Allowed only $supportTypes archives, but given *.$extension", 400);
}
$hash = sha1(random_bytes(30));
@@ -85,13 +86,13 @@ public function save(File $file): array
try {
$file->move($this->tmpDir, $filename);
$fullname = PacketonUtils::buildPath($this->tmpDir, $filename);
+
if (!$this->artifactStorage->fileExists($filename)) {
$stream = fopen($fullname, 'r');
$this->artifactStorage->writeStream($filename, $stream);
}
-
} catch (\Exception $e) {
- return ['code' => 400, 'error' => $e->getMessage()];
+ throw new ZipballException($e->getMessage(), 400, $e);
}
$zipball = new Zipball();
@@ -106,11 +107,7 @@ public function save(File $file): array
$manager->persist($zipball);
$manager->flush();
- return [
- 'id' => $zipball->getId(),
- 'filename' => $zipball->getOriginalFilename(),
- 'size' => $zipball->getFileSize(),
- ];
+ return $zipball;
}
protected function guessExtension(File $file, ?string $mimeType): ?string
diff --git a/src/Repository/PackageRepository.php b/src/Repository/PackageRepository.php
index 45b3a4cf..4b7f13d5 100644
--- a/src/Repository/PackageRepository.php
+++ b/src/Repository/PackageRepository.php
@@ -300,16 +300,16 @@ public function getPartialPackageByNameWithVersions($name)
return $pkg;
}
- public function getPackageByName($name)
+ public function getPackageByName(string $name): ?Package
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('p', 'm')
- ->from('Packeton\Entity\Package', 'p')
+ ->from(Package::class, 'p')
->leftJoin('p.maintainers', 'm')
->where('p.name = ?0')
->setParameters(array($name));
- return $qb->getQuery()->getSingleResult();
+ return $qb->getQuery()->getOneOrNullResult();
}
public function getPackagesWithVersions(?array $ids = null, $filters = [])
diff --git a/src/Service/Artifact/ArtifactPushHandler.php b/src/Service/Artifact/ArtifactPushHandler.php
index 9e587ee0..16c7bbb7 100644
--- a/src/Service/Artifact/ArtifactPushHandler.php
+++ b/src/Service/Artifact/ArtifactPushHandler.php
@@ -4,21 +4,97 @@
namespace Packeton\Service\Artifact;
+use Composer\IO\IOInterface;
+use Composer\Util\Tar;
+use Composer\Util\Zip;
+use Doctrine\Persistence\ManagerRegistry;
use Packeton\Entity\Package;
+use Packeton\Entity\Zipball;
+use Packeton\Exception\ValidationException;
use Packeton\Form\Model\PushRequestDtoInterface;
use Packeton\Model\UploadZipballStorage;
+use Packeton\Package\RepTypes;
+use Packeton\Service\Scheduler;
class ArtifactPushHandler
{
public function __construct(
- private readonly UploadZipballStorage $storage
+ private readonly UploadZipballStorage $storage,
+ private readonly ManagerRegistry $registry,
+ private readonly Scheduler $scheduler,
) {
}
- public function addVersion(PushRequestDtoInterface $requestDto, Package $package, ?string $version = null): void
+ public function addVersion(PushRequestDtoInterface $requestDto, Package $package): void
{
- $version ??= 'dev-master';
+ $zip = $this->storage->save($requestDto->getArtifact());
+ $zip->setUsed(true);
- $this->storage->save($requestDto->getArtifact());
+ $path = $this->storage->moveToLocal($zip);
+ if (!file_exists($path)) {
+ throw new ValidationException("Unable to create archive file", 400);
+ }
+
+ switch ($package->getRepoType()) {
+ case RepTypes::ARTIFACT:
+ $package->setArchiveOverwrite(
+ $zip->getId(),
+ $this->generateArchiveData($requestDto)
+ );
+ break;
+ case RepTypes::CUSTOM:
+ $package->addCustomVersions(
+ $this->generateCustomData($requestDto, $zip, $path)
+ );
+ break;
+ default:
+ throw new ValidationException("Support only CUSTOM and ARTIFACT repository types", 400);
+ }
+
+ $manager = $this->registry->getManager();
+ $manager->flush();
+
+ $this->scheduler->scheduleUpdate($package);
+ }
+
+ private function generateArchiveData(PushRequestDtoInterface $requestDto): array
+ {
+ return [
+ 'version' => $requestDto->getPackageVersion(),
+ 'source' => $requestDto->getSource(),
+ ];
+ }
+
+ private function generateCustomData(PushRequestDtoInterface $requestDto, Zipball $zipball, string $path): array
+ {
+ $file = new \SplFileInfo($path);
+ $fileExtension = pathinfo($file->getPathname(), PATHINFO_EXTENSION);
+
+ if (in_array($fileExtension, ['gz', 'tar', 'tgz'], true)) {
+ $fileType = 'tar';
+ } elseif ($fileExtension === 'zip') {
+ $fileType = 'zip';
+ } else {
+ throw new ValidationException('Files with "'.$fileExtension.'" extensions aren\'t supported. Only ZIP and TAR/TAR.GZ/TGZ archives are supported.');
+ }
+
+ try {
+ $json = $fileType === 'tar' ? Tar::getComposerJson($file->getPathname()) : Zip::getComposerJson($file->getPathname());
+ } catch (\Throwable $e) {
+ throw new ValidationException('Failed loading package '.$file->getPathname().': '.$e->getMessage(), previous: $e);
+ }
+
+ $json = json_decode($json, true) ?? [];
+
+ $json['version'] = $requestDto->getPackageVersion();
+ $json['source'] ??= $requestDto->getSource();
+
+ unset($json['extra']['push']);
+
+ return [
+ 'version' => $requestDto->getPackageVersion(),
+ 'dist' => $zipball->getId(),
+ 'definition' => $json,
+ ];
}
}
diff --git a/src/Service/SwaggerDumper.php b/src/Service/SwaggerDumper.php
index 45c04c83..1fb56e3a 100644
--- a/src/Service/SwaggerDumper.php
+++ b/src/Service/SwaggerDumper.php
@@ -83,7 +83,14 @@ protected function wrapExamples(array $spec): array
foreach ($resources as $name => $resource) {
if (isset($resource['example'])) {
- $example = ($isRef = str_starts_with($resource['example'], '$')) ? $resource['example'] : json_decode($resource['example']);
+ $isRef = false;
+ if (is_string($resource['example'])) {
+ $example = ($isRef = str_starts_with($resource['example'], '$')) ? $resource['example'] :
+ (null !== json_decode($resource['example']) ? json_decode($resource['example']) : $resource['example']);
+ } else {
+ $example = $resource['example'];
+ }
+
$hash = sha1(serialize($example));
unset($resource['example']);
if ($example === null) {
@@ -115,8 +122,15 @@ protected function wrapExamples(array $spec): array
return $spec;
}
- protected function dumpExample($example)
+ protected function dumpExample(mixed $example)
{
+ if (is_string($example)) {
+ return [
+ 'type' => 'string',
+ 'format' => 'binary'
+ ];
+ }
+
$obj = [
'type' => 'object',
'properties' => []
diff --git a/src/Util/PacketonUtils.php b/src/Util/PacketonUtils.php
index 10a43159..58c99623 100644
--- a/src/Util/PacketonUtils.php
+++ b/src/Util/PacketonUtils.php
@@ -216,7 +216,15 @@ public static function buildPath(string $baseDir, ...$paths): string
public static function buildChoices(array $listOf, string $key, ?string $value = null): array
{
- return array_combine(array_column($listOf, $key), $value ? array_column($listOf, $value) : $listOf);
+ $result = [];
+ foreach ($listOf as $item) {
+ if (!isset($item[$key])) {
+ continue;
+ }
+ $result[$item[$key]] = null === $value ? $item : ($item[$value] ?? null);
+ }
+
+ return $result;
}
public static function matchGlobAll(array $listOf, null|string|array $globs, string|array|null $excluded = null): array
diff --git a/swagger/packages-api.yaml b/swagger/packages-api.yaml
index ec42fcf6..c259dc16 100644
--- a/swagger/packages-api.yaml
+++ b/swagger/packages-api.yaml
@@ -51,7 +51,7 @@ paths:
tags: [ Packages ]
summary: 'Create a package'
example: $PackageCreate
-
+
'/api/packages':
get:
tags: [ Packages ]
@@ -84,12 +84,26 @@ paths:
example: $PackageUpdate
<<: *pkg-param
+ '/api/packages/upload/{name}/{version}':
+ put:
+ tags: [ Packages ]
+ summary: 'Put a new version to package and upload zip artifact. Support custom JSON and artifact package type. Request body is '
+ parameters:
+ - name: name
+ in: path
+ required: true
+ type: string
+ - name: version
+ description: version, example v2.8.0
+ in: query
+ example: ""
+
'/api/packages/{name}/dependents':
get:
tags: [ Packages ]
summary: 'View the dependents of a package'
<<: *pkg-param
-
+
'/api/packages/{name}/changelog':
get:
tags: [ Packages ]