From 9ac50ea39423b23481f4fa0c25586c58120ea33d Mon Sep 17 00:00:00 2001 From: Ron Trevor Date: Fri, 6 Mar 2026 22:06:16 +0000 Subject: [PATCH 01/16] First version, wip --- appinfo/routes.php | 7 ++ lib/Backend/BeeSwarm.php | 6 ++ lib/Controller/StorageController.php | 140 +++++++++++++++++++++++++++ lib/Storage/BeeSwarmTrait.php | 23 +++++ 4 files changed, 176 insertions(+) create mode 100644 lib/Controller/StorageController.php diff --git a/appinfo/routes.php b/appinfo/routes.php index 8bb51fe..5f8194e 100755 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -34,4 +34,11 @@ 'verb' => 'POST', ], ], + 'ocs' => [ + [ + 'name' => 'Storage#create', + 'url' => '/api/v1/storages', + 'verb' => 'POST', + ], + ], ]; diff --git a/lib/Backend/BeeSwarm.php b/lib/Backend/BeeSwarm.php index fa49329..edd3d89 100755 --- a/lib/Backend/BeeSwarm.php +++ b/lib/Backend/BeeSwarm.php @@ -27,11 +27,14 @@ use OCA\Files_External\Service\GlobalStoragesService; use OCA\Files_External_Ethswarm\AppInfo\Application; use OCA\Files_External_Ethswarm\Auth\AccessKey; +use OCA\Files_External_Ethswarm\Storage\BeeSwarmTrait; use OCP\IConfig; use OCP\IL10N; use Psr\Log\LoggerInterface; class BeeSwarm extends Backend { + use BeeSwarmTrait; + /** @const string */ public const OPTION_HOST_URL = 'host_url'; @@ -67,6 +70,9 @@ public function __construct(string $appName, IL10N $l, IConfig $config, LoggerIn } public function validateStorageDefinition(StorageConfig $storage): bool { + + $this->createStorage(); + $result = true; // access key diff --git a/lib/Controller/StorageController.php b/lib/Controller/StorageController.php new file mode 100644 index 0000000..d5c4b59 --- /dev/null +++ b/lib/Controller/StorageController.php @@ -0,0 +1,140 @@ +globalStoragesService = $globalStoragesService; + $this->userSession = $userSession; + $this->logger = $logger; + } + + /** + * Create a new Swarm external storage + * + * @param string $mountPoint The folder name/mount point (e.g., "/MySwarmStorage") + * @param string $accessKey The Swarm access key + * @param string $hostUrl The Access Server URL (e.g., "app.hejbit.com") + * @return DataResponse + */ + #[NoAdminRequired] + public function create( + string $mountPoint, + string $accessKey, + string $hostUrl + ): DataResponse { + // Validate required parameters + if (empty($mountPoint)) { + return new DataResponse([ + 'ocs' => [ + 'meta' => [ + 'status' => 'failure', + 'statuscode' => 400, + 'message' => 'Mount point is required' + ] + ] + ], 400); + } + + if (empty($accessKey)) { + return new DataResponse([ + 'ocs' => [ + 'meta' => [ + 'status' => 'failure', + 'statuscode' => 400, + 'message' => 'Access key is required' + ] + ] + ], 400); + } + + if (empty($hostUrl)) { + return new DataResponse([ + 'ocs' => [ + 'meta' => [ + 'status' => 'failure', + 'statuscode' => 400, + 'message' => 'Access key is required' + ] + ] + ], 400); + } + + // Ensure mount point starts with / + if (strpos($mountPoint, '/') !== 0) { + $mountPoint = '/' . $mountPoint; + } + + try { + // Set as personal storage (current user only) + $user = $this->userSession->getUser(); + $user = [$user->getUID()]; + + // Create StorageConfig + $storageConfig = new StorageConfig(); + $storageConfig = $this->globalStoragesService->createStorage( + $mountPoint, + APPLICATION::NAME, + 'access:key', + [ + 'access_key' => $accessKey, + 'host_url' => $hostUrl ?: 'app.hejbit.com' + ], + null, + $user + ); + + // Add the storage via the service + $newStorage = $this->globalStoragesService->addStorage($storageConfig); + + $this->logger->info('Swarm storage created: ' . $mountPoint); + + return new DataResponse([ + 'ocs' => [ + 'meta' => [ + 'status' => 'success', + 'statuscode' => 201, + 'message' => 'Storage created successfully' + ] + ], + 'data' => $newStorage->jsonSerialize(true) + ], 201); + + } catch (\Exception $e) { + $this->logger->error('Failed to create storage: ' . $e->getMessage()); + + return new DataResponse([ + 'ocs' => [ + 'meta' => [ + 'status' => 'failure', + 'statuscode' => 500, + 'message' => $e->getMessage() + ] + ] + ], 500); + } + } +} diff --git a/lib/Storage/BeeSwarmTrait.php b/lib/Storage/BeeSwarmTrait.php index 85c96ea..866b4f9 100755 --- a/lib/Storage/BeeSwarmTrait.php +++ b/lib/Storage/BeeSwarmTrait.php @@ -243,4 +243,27 @@ private function uploadSwarmV1(string $path, string $tempFile, string $mimetype) return $reference; } + + private function createStorage() { + $endpoint = "/ocs/v2.php/apps/files_external_ethswarm/api/v1/storages"; + + $data = array("mountPoint"=>"MySwarmFolder", "accessKey"=>"your-access-key-here", "hostUrl"=>"app.hejbit.com"); + + $request = new Curl($endpoint, [ + CURLOPT_PUT => true, + CURLOPT_POST => true, + ], [ + 'content-type: application/x-www-form-urlencoded', + //'content-length: ' . strlen($data) + ]); + + $response = $request->post($data, true); + + $httpCode = $request->getInfo(CURLINFO_HTTP_CODE); + if (200 !== $httpCode) { + throw new HejBitException('Failed to create Swarm storage'); + } + + return $response; + } } From 990e9b48834fe662772faae5eeeb3c9d49a57ed9 Mon Sep 17 00:00:00 2001 From: Ron Trevor Date: Tue, 10 Mar 2026 22:40:30 +0000 Subject: [PATCH 02/16] feat(#1989): create api for new Swarm storage folder. - add: conforms to OCS OpenAPI standard - add: add composer packages for OCS extractor --- appinfo/routes.php | 14 +- composer.json | 8 +- lib/Controller/StorageController.php | 292 ++++++++++-------- openapi.json | 431 +++++++++++++++++++++++++++ 4 files changed, 614 insertions(+), 131 deletions(-) create mode 100644 openapi.json diff --git a/appinfo/routes.php b/appinfo/routes.php index 5f8194e..cf6467f 100755 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -34,11 +34,11 @@ 'verb' => 'POST', ], ], - 'ocs' => [ - [ - 'name' => 'Storage#create', - 'url' => '/api/v1/storages', - 'verb' => 'POST', - ], - ], + 'ocs' => [ + [ + 'name' => 'Storage#create', + 'url' => '/api/v1/storages', + 'verb' => 'POST', + ], + ], ]; diff --git a/composer.json b/composer.json index 9980f2f..df12e18 100644 --- a/composer.json +++ b/composer.json @@ -8,13 +8,15 @@ } }, "require": { - "ext-curl": "*" + "ext-curl": "*", + "ext-simplexml": "*" }, "require-dev": { "nextcloud/coding-standard": "^1.3.2", "ext-fileinfo": "*", "friendsofphp/php-cs-fixer": "*", - "bamarni/composer-bin-plugin": "^1.8" + "bamarni/composer-bin-plugin": "^1.8", + "vimeo/psalm": "*" }, "scripts": { "cs:check": "./vendor/php-cs-fixer/shim/php-cs-fixer.phar fix --dry-run --diff", @@ -38,4 +40,4 @@ "forward-command": true } } -} +} \ No newline at end of file diff --git a/lib/Controller/StorageController.php b/lib/Controller/StorageController.php index d5c4b59..b46c5c1 100644 --- a/lib/Controller/StorageController.php +++ b/lib/Controller/StorageController.php @@ -2,139 +2,189 @@ declare(strict_types=1); +/** + * @copyright Copyright (c) 2022, MetaProvide Holding EKF + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + namespace OCA\Files_External_Ethswarm\Controller; -use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\Service\GlobalStoragesService; use OCA\Files_External_Ethswarm\AppInfo\Application; +use OCA\Files_External_Ethswarm\Auth\AccessKey; +use OCA\Files_External_Ethswarm\Backend\BeeSwarm; use OCP\AppFramework\OCSController; use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\ApiRoute; use OCP\AppFramework\Http\DataResponse; use OCP\IRequest; use OCP\IUserSession; use Psr\Log\LoggerInterface; -class StoragesController extends OCSController { - private GlobalStoragesService $globalStoragesService; - private IUserSession $userSession; - private LoggerInterface $logger; - - public function __construct( - string $appName, - IRequest $request, - GlobalStoragesService $globalStoragesService, - IUserSession $userSession, - LoggerInterface $logger - ) { - parent::__construct($appName, $request); - $this->globalStoragesService = $globalStoragesService; - $this->userSession = $userSession; - $this->logger = $logger; - } - - /** - * Create a new Swarm external storage - * - * @param string $mountPoint The folder name/mount point (e.g., "/MySwarmStorage") - * @param string $accessKey The Swarm access key - * @param string $hostUrl The Access Server URL (e.g., "app.hejbit.com") - * @return DataResponse - */ - #[NoAdminRequired] - public function create( - string $mountPoint, - string $accessKey, - string $hostUrl - ): DataResponse { - // Validate required parameters - if (empty($mountPoint)) { - return new DataResponse([ - 'ocs' => [ - 'meta' => [ - 'status' => 'failure', - 'statuscode' => 400, - 'message' => 'Mount point is required' - ] - ] - ], 400); - } - - if (empty($accessKey)) { - return new DataResponse([ - 'ocs' => [ - 'meta' => [ - 'status' => 'failure', - 'statuscode' => 400, - 'message' => 'Access key is required' - ] - ] - ], 400); - } - - if (empty($hostUrl)) { - return new DataResponse([ - 'ocs' => [ - 'meta' => [ - 'status' => 'failure', - 'statuscode' => 400, - 'message' => 'Access key is required' - ] - ] - ], 400); - } - - // Ensure mount point starts with / - if (strpos($mountPoint, '/') !== 0) { - $mountPoint = '/' . $mountPoint; - } - - try { - // Set as personal storage (current user only) - $user = $this->userSession->getUser(); - $user = [$user->getUID()]; - - // Create StorageConfig - $storageConfig = new StorageConfig(); +class StorageController extends OCSController { + private GlobalStoragesService $globalStoragesService; + private IUserSession $userSession; + private LoggerInterface $logger; + + public function __construct( + string $appName, + IRequest $request, + GlobalStoragesService $globalStoragesService, + IUserSession $userSession, + LoggerInterface $logger + ) { + parent::__construct($appName, $request); + $this->globalStoragesService = $globalStoragesService; + $this->userSession = $userSession; + $this->logger = $logger; + } + + /** + * Create a new Hejbit Swarm external storage + * + * @param string $folderName The folder name/mount point for the storage + * @param string $accessKey The Hejbit access key for authentication + * @param string $hostUrl The Access Server URL (e.g., "app.hejbit.com") + * @return DataResponse + * @return DataResponse}}, array{}> + * @return DataResponse}}, array{}> + * @return DataResponse}}, array{}> + * + * 201: Storage created successfully + * 400: Bad request (missing parameters or invalid URL) + * 401: Unauthorized (user not authenticated) + * 500: Internal server error (failed to create storage) + */ + #[NoAdminRequired] + public function create( + string $folderName, + string $accessKey, + string $hostUrl + ): DataResponse { + // Validate required parameters + $validationError = $this->validateParameters($folderName, $accessKey, $hostUrl); + if ($validationError !== null) { + return $validationError; + } + + // Validate host URL format + $validatedHost = $this->validateHostUrl($hostUrl); + if ($validatedHost === null) { + return $this->errorResponse('Invalid host URL format', 400); + } + + // Ensure mount point starts with / + $mountPoint = '/' . ltrim($folderName, '/'); + + try { + // Get the current user + $user = $this->userSession->getUser(); + if ($user === null) { + return $this->errorResponse('User not authenticated', 401); + } + + // Create storage using GlobalStoragesService $storageConfig = $this->globalStoragesService->createStorage( $mountPoint, - APPLICATION::NAME, + Application::NAME, 'access:key', - [ - 'access_key' => $accessKey, - 'host_url' => $hostUrl ?: 'app.hejbit.com' - ], + [BeeSwarm::OPTION_HOST_URL => $hostUrl, + AccessKey::SCHEME => $accessKey], null, - $user - ); - - // Add the storage via the service - $newStorage = $this->globalStoragesService->addStorage($storageConfig); - - $this->logger->info('Swarm storage created: ' . $mountPoint); - - return new DataResponse([ - 'ocs' => [ - 'meta' => [ - 'status' => 'success', - 'statuscode' => 201, - 'message' => 'Storage created successfully' - ] - ], - 'data' => $newStorage->jsonSerialize(true) - ], 201); - - } catch (\Exception $e) { - $this->logger->error('Failed to create storage: ' . $e->getMessage()); - - return new DataResponse([ - 'ocs' => [ - 'meta' => [ - 'status' => 'failure', - 'statuscode' => 500, - 'message' => $e->getMessage() - ] - ] - ], 500); - } - } -} + [] // Empty array = all users + ); + + // Add the storage via the service + $newStorage = $this->globalStoragesService->addStorage($storageConfig); + + $this->logger->info('Swarm storage created successfully: ' . $mountPoint . ' for user: ' . $user->getUID()); + + return $this->successResponse([ + 'id' => $newStorage->getId(), + 'mountPoint' => $newStorage->getMountPoint(), + 'backend' => Application::NAME, + ]); + } catch (\Exception $e) { + $this->logger->error('Failed to create Swarm storage: ' . $e->getMessage(), [ + 'exception' => $e, + 'folderName' => $folderName, + 'hostUrl' => $hostUrl + ]); + + return $this->errorResponse('Failed to create storage: ' . $e->getMessage(), 500); + } + } + + /** + * Validate required parameters + */ + private function validateParameters(string $folderName, string $accessKey, string $hostUrl): ?DataResponse { + if (empty($folderName)) { + return $this->errorResponse('Folder name is required', 400); + } + if (empty($accessKey)) { + return $this->errorResponse('Access key is required', 400); + } + if (empty($hostUrl)) { + return $this->errorResponse('Host URL is required', 400); + } + return null; + } + + /** + * Validate and normalize host URL + * @return string|null Normalized URL or null if invalid + */ + private function validateHostUrl(string $hostUrl): ?string { + $validatedHost = $hostUrl; + if (!preg_match('/^https?:\/\//i', $validatedHost)) { + $validatedHost = 'https://' . $validatedHost; + } + return filter_var($validatedHost, FILTER_VALIDATE_URL) ? $validatedHost : null; + } + + /** + * Create a success response + */ + private function successResponse(array $data): DataResponse { + return new DataResponse([ + 'ocs' => [ + 'meta' => [ + 'status' => 'success', + 'statuscode' => 201, + 'message' => 'Storage created successfully' + ], + 'data' => $data + ] + ], 201); + } + + /** + * Create an error response + */ + private function errorResponse(string $message, int $statusCode): DataResponse { + return new DataResponse([ + 'ocs' => [ + 'meta' => [ + 'status' => 'failure', + 'statuscode' => $statusCode, + 'message' => $message + ], + 'data' => [] + ] + ], $statusCode); + } +} \ No newline at end of file diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..9a8e752 --- /dev/null +++ b/openapi.json @@ -0,0 +1,431 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "files_external_ethswarm", + "version": "0.0.1", + "description": "Bring decentralized, sovereign cloud storage to Nextcloud with the HejBit Swarm plugin!", + "license": { + "name": "agpl" + } + }, + "components": { + "securitySchemes": { + "basic_auth": { + "type": "http", + "scheme": "basic" + }, + "bearer_auth": { + "type": "http", + "scheme": "bearer" + } + }, + "schemas": { + "OCSMeta": { + "type": "object", + "required": [ + "status", + "statuscode" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "totalitems": { + "type": "string" + }, + "itemsperpage": { + "type": "string" + } + } + } + } + }, + "paths": { + "/ocs/v2.php/apps/files_external_ethswarm/api/v1/storages": { + "post": { + "operationId": "storage-create", + "summary": "Create a new Hejbit Swarm external storage", + "tags": [ + "storage" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "folderName", + "accessKey", + "hostUrl" + ], + "properties": { + "folderName": { + "type": "string", + "description": "The folder name/mount point for the storage" + }, + "accessKey": { + "type": "string", + "description": "The Hejbit access key for authentication" + }, + "hostUrl": { + "type": "string", + "description": "The Access Server URL (e.g., \"app.hejbit.com\")" + } + } + } + } + } + }, + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "201": { + "description": "Storage created successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "type": "object", + "required": [ + "status", + "statuscode", + "message" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + } + } + }, + "data": { + "type": "object", + "required": [ + "id", + "mountPoint", + "backend" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "mountPoint": { + "type": "string" + }, + "backend": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request (missing parameters or invalid URL)", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "type": "object", + "required": [ + "status", + "statuscode", + "message" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + } + } + }, + "data": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized (user not authenticated)", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "type": "object", + "required": [ + "status", + "statuscode", + "message" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + } + } + }, + "data": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + } + }, + { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + ] + } + } + } + }, + "500": { + "description": "Internal server error (failed to create storage)", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "type": "object", + "required": [ + "status", + "statuscode", + "message" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + } + } + }, + "data": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "tags": [] +} From 6c61f3b87699562e20a1e841186c37b2b85d389e Mon Sep 17 00:00:00 2001 From: Ron Trevor Date: Tue, 10 Mar 2026 22:53:21 +0000 Subject: [PATCH 03/16] feat(#1989): create api for new Swarm storage folder. - reverse previous commit --- lib/Storage/BeeSwarmTrait.php | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/lib/Storage/BeeSwarmTrait.php b/lib/Storage/BeeSwarmTrait.php index 866b4f9..85c96ea 100755 --- a/lib/Storage/BeeSwarmTrait.php +++ b/lib/Storage/BeeSwarmTrait.php @@ -243,27 +243,4 @@ private function uploadSwarmV1(string $path, string $tempFile, string $mimetype) return $reference; } - - private function createStorage() { - $endpoint = "/ocs/v2.php/apps/files_external_ethswarm/api/v1/storages"; - - $data = array("mountPoint"=>"MySwarmFolder", "accessKey"=>"your-access-key-here", "hostUrl"=>"app.hejbit.com"); - - $request = new Curl($endpoint, [ - CURLOPT_PUT => true, - CURLOPT_POST => true, - ], [ - 'content-type: application/x-www-form-urlencoded', - //'content-length: ' . strlen($data) - ]); - - $response = $request->post($data, true); - - $httpCode = $request->getInfo(CURLINFO_HTTP_CODE); - if (200 !== $httpCode) { - throw new HejBitException('Failed to create Swarm storage'); - } - - return $response; - } } From c4b8670147f7cc1818c449bace0cf13d892c3fc3 Mon Sep 17 00:00:00 2001 From: Ron Trevor Date: Tue, 10 Mar 2026 22:57:24 +0000 Subject: [PATCH 04/16] feat(#1989): create api for new Swarm storage folder. - reverse previous commit --- lib/Backend/BeeSwarm.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/Backend/BeeSwarm.php b/lib/Backend/BeeSwarm.php index edd3d89..0b96b60 100755 --- a/lib/Backend/BeeSwarm.php +++ b/lib/Backend/BeeSwarm.php @@ -27,14 +27,11 @@ use OCA\Files_External\Service\GlobalStoragesService; use OCA\Files_External_Ethswarm\AppInfo\Application; use OCA\Files_External_Ethswarm\Auth\AccessKey; -use OCA\Files_External_Ethswarm\Storage\BeeSwarmTrait; use OCP\IConfig; use OCP\IL10N; use Psr\Log\LoggerInterface; class BeeSwarm extends Backend { - use BeeSwarmTrait; - /** @const string */ public const OPTION_HOST_URL = 'host_url'; @@ -71,8 +68,6 @@ public function __construct(string $appName, IL10N $l, IConfig $config, LoggerIn public function validateStorageDefinition(StorageConfig $storage): bool { - $this->createStorage(); - $result = true; // access key From 1d6dd420d5aa0b7ea8f316ee1736e7a94d2fef4b Mon Sep 17 00:00:00 2001 From: Ron Trevor Date: Tue, 10 Mar 2026 22:58:54 +0000 Subject: [PATCH 05/16] feat(#1989): create api for new Swarm storage folder. - reverse previous commit --- lib/Backend/BeeSwarm.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Backend/BeeSwarm.php b/lib/Backend/BeeSwarm.php index 0b96b60..fa49329 100755 --- a/lib/Backend/BeeSwarm.php +++ b/lib/Backend/BeeSwarm.php @@ -67,7 +67,6 @@ public function __construct(string $appName, IL10N $l, IConfig $config, LoggerIn } public function validateStorageDefinition(StorageConfig $storage): bool { - $result = true; // access key From ef1ee79430d3ebba40e39de67e80ec49f9e04f2e Mon Sep 17 00:00:00 2001 From: Mahyar Iranibazaz Date: Wed, 18 Mar 2026 18:45:59 -0300 Subject: [PATCH 06/16] chore: precommit setup and lint fixes --- package.json | 5 ++++- src/util/FilesHelper.ts | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index a8c68f9..60b2177 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,13 @@ "dev": "vite build --mode development --watch", "serve": "vite --mode development --host 127.0.0.1", "check": "pnpm run format && pnpm run lint && pnpm run typecheck", + "precommit": "pnpm run format:fix && pnpm run backend:format:fix && pnpm run lint && pnpm run typecheck", "lint": "biome lint src styles", "lint:fix": "biome lint --write src styles", "format": "biome format .", "format:fix": "biome format --write .", + "backend:format": "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --dry-run", + "backend:format:fix": "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix", "typecheck": "vue-tsc --noEmit", "prepare": "simple-git-hooks" }, @@ -54,6 +57,6 @@ "extends @nextcloud/browserslist-config" ], "simple-git-hooks": { - "pre-commit": "pnpm run check" + "pre-commit": "pnpm run precommit" } } diff --git a/src/util/FilesHelper.ts b/src/util/FilesHelper.ts index 4a8dbcb..28eaf42 100644 --- a/src/util/FilesHelper.ts +++ b/src/util/FilesHelper.ts @@ -1,6 +1,6 @@ +import { basename, dirname } from "node:path"; import { getFilePickerBuilder } from "@nextcloud/dialogs"; import { FileType } from "@nextcloud/files"; -import { basename, dirname } from "path"; import SvgHelper from "@/util/SvgHelper"; const FilesHelper = { @@ -77,8 +77,8 @@ const FilesHelper = { .allowDirectories(true) .setFilter((n) => { const isNotArchiveFolder = !FilesHelper.isArchiveFolder(n); - console.log("node:" + FilesHelper.getStoragePath(n)); - console.log("file:" + FilesHelper.getStoragePath(node)); + console.log(`node:${FilesHelper.getStoragePath(n)}`); + console.log(`file:${FilesHelper.getStoragePath(node)}`); const isSameStorage = FilesHelper.getStoragePath(n) === FilesHelper.getStoragePath(node); From 7a1beb86373907f532fb4005bf9b08090a3e0bc8 Mon Sep 17 00:00:00 2001 From: Ron Trevor Date: Fri, 20 Mar 2026 13:01:27 +0000 Subject: [PATCH 07/16] feat(#1989): add ConfigurationController for storage creation - add: new ConfigurationController with create endpoint with redirect to NC URL. - add: corresponding route in routes.php --- appinfo/routes.php | 5 ++ lib/Controller/ConfigurationController.php | 71 ++++++++++++++++++++++ lib/Controller/StorageController.php | 3 +- 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 lib/Controller/ConfigurationController.php diff --git a/appinfo/routes.php b/appinfo/routes.php index cf6467f..f0aa2eb 100755 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -33,6 +33,11 @@ 'url' => '/feedback/submit', 'verb' => 'POST', ], + [ + 'name' => 'Configuration#create', + 'url' => 'configure/create', + 'verb' => 'GET', + ], ], 'ocs' => [ [ diff --git a/lib/Controller/ConfigurationController.php b/lib/Controller/ConfigurationController.php new file mode 100644 index 0000000..8c72c88 --- /dev/null +++ b/lib/Controller/ConfigurationController.php @@ -0,0 +1,71 @@ +storageController = $storageController; + $this->urlGenerator = $urlGenerator; + } + + /** + * Create a storage definition by delegating to StorageController::create() + * + * @param array $params Array containing 'key', 'folder' (optional), and 'hosturl' + * @return JSONResponse|RedirectResponse JSON response with status and message, or redirect + */ + #[NoCSRFRequired] + public function create(): RedirectResponse|JSONResponse + { + $params = $this->request->getParams(); + $key = trim($params['key'] ?? ''); + $folder = trim($params['folder'] ?? 'Hejbit-Storage'); + $hosturl = trim($params['hosturl'] ?? 'app.hejbit.com'); + + try { + $dataResponse = $this->storageController->create($folder, $key, $hosturl); + + // Extract status and message from DataResponse + $data = $dataResponse->getData(); + $meta = $data['ocs']['meta'] ?? []; + $status = $dataResponse->getStatus(); + + // Redirect to NC external storage mounts page after creation + $redirectUrl = $this->urlGenerator->getAbsoluteURL('/apps/files/extstoragemounts'); + + // TODO: Determine how to send $dataResponse parameters to the caller or the redirect URL. + // For now, send them as querystring parameters for demonstration/debug purposes. + $redirectUrl .= '?status=' . urlencode($meta['status'] ?? 'unknown') . '&message=' . urlencode($meta['message'] ?? ''); + return new RedirectResponse($redirectUrl); + + // To return a JSON response to the caller (instead of redirecting): + /*return new JSONResponse([ + 'status' => $meta['status'] ?? 'unknown', + 'message' => $meta['message'] ?? '' + ], $status);*/ + } catch (Throwable $e) { + return new JSONResponse(['status' => 'error', 'message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); + } + } +} diff --git a/lib/Controller/StorageController.php b/lib/Controller/StorageController.php index b46c5c1..4802eb7 100644 --- a/lib/Controller/StorageController.php +++ b/lib/Controller/StorageController.php @@ -28,7 +28,6 @@ use OCA\Files_External_Ethswarm\Backend\BeeSwarm; use OCP\AppFramework\OCSController; use OCP\AppFramework\Http\Attribute\NoAdminRequired; -use OCP\AppFramework\Http\Attribute\ApiRoute; use OCP\AppFramework\Http\DataResponse; use OCP\IRequest; use OCP\IUserSession; @@ -101,7 +100,7 @@ public function create( $mountPoint, Application::NAME, 'access:key', - [BeeSwarm::OPTION_HOST_URL => $hostUrl, + [BeeSwarm::OPTION_HOST_URL => $hostUrl, AccessKey::SCHEME => $accessKey], null, [] // Empty array = all users From d0709505bd75875b203a32da87afa87c41d426e3 Mon Sep 17 00:00:00 2001 From: Ron Trevor Date: Sat, 21 Mar 2026 12:21:49 +0000 Subject: [PATCH 08/16] chore(#1989): Run php-cs-fixer on committed files --- appinfo/routes.php | 2 +- lib/Controller/ConfigurationController.php | 95 +++++++++++----------- lib/Controller/StorageController.php | 59 ++++++++------ 3 files changed, 79 insertions(+), 77 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index f0aa2eb..253cdaf 100755 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -37,7 +37,7 @@ 'name' => 'Configuration#create', 'url' => 'configure/create', 'verb' => 'GET', - ], + ], ], 'ocs' => [ [ diff --git a/lib/Controller/ConfigurationController.php b/lib/Controller/ConfigurationController.php index 8c72c88..e31cee1 100644 --- a/lib/Controller/ConfigurationController.php +++ b/lib/Controller/ConfigurationController.php @@ -5,67 +5,64 @@ namespace OCA\Files_External_Ethswarm\Controller; use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\JSONResponse; -use OCP\AppFramework\Http; use OCP\AppFramework\Http\RedirectResponse; use OCP\IRequest; use OCP\IURLGenerator; use Throwable; -class ConfigurationController extends Controller -{ - private StorageController $storageController; - private IURLGenerator $urlGenerator; +class ConfigurationController extends Controller { + private StorageController $storageController; + private IURLGenerator $urlGenerator; - public function __construct( - string $appName, - IRequest $request, - StorageController $storageController, - IURLGenerator $urlGenerator - ) { - parent::__construct($appName, $request); - $this->storageController = $storageController; - $this->urlGenerator = $urlGenerator; - } + public function __construct( + string $appName, + IRequest $request, + StorageController $storageController, + IURLGenerator $urlGenerator + ) { + parent::__construct($appName, $request); + $this->storageController = $storageController; + $this->urlGenerator = $urlGenerator; + } - /** - * Create a storage definition by delegating to StorageController::create() - * - * @param array $params Array containing 'key', 'folder' (optional), and 'hosturl' - * @return JSONResponse|RedirectResponse JSON response with status and message, or redirect - */ - #[NoCSRFRequired] - public function create(): RedirectResponse|JSONResponse - { - $params = $this->request->getParams(); - $key = trim($params['key'] ?? ''); - $folder = trim($params['folder'] ?? 'Hejbit-Storage'); - $hosturl = trim($params['hosturl'] ?? 'app.hejbit.com'); + /** + * Create a storage definition by delegating to StorageController::create(). + * + * @return JSONResponse|RedirectResponse JSON response with status and message, or redirect + */ + #[NoCSRFRequired] + public function create(): JSONResponse|RedirectResponse { + $params = $this->request->getParams(); + $key = trim($params['key'] ?? ''); + $folder = trim($params['folder'] ?? 'Hejbit-Storage'); + $hosturl = trim($params['hosturl'] ?? 'app.hejbit.com'); - try { - $dataResponse = $this->storageController->create($folder, $key, $hosturl); + try { + $dataResponse = $this->storageController->create($folder, $key, $hosturl); - // Extract status and message from DataResponse - $data = $dataResponse->getData(); - $meta = $data['ocs']['meta'] ?? []; - $status = $dataResponse->getStatus(); + // Extract status and message from DataResponse + $data = $dataResponse->getData(); + $meta = $data['ocs']['meta'] ?? []; + $status = $dataResponse->getStatus(); - // Redirect to NC external storage mounts page after creation - $redirectUrl = $this->urlGenerator->getAbsoluteURL('/apps/files/extstoragemounts'); + // Redirect to NC external storage mounts page after creation + $redirectUrl = $this->urlGenerator->getAbsoluteURL('/apps/files/extstoragemounts'); - // TODO: Determine how to send $dataResponse parameters to the caller or the redirect URL. - // For now, send them as querystring parameters for demonstration/debug purposes. - $redirectUrl .= '?status=' . urlencode($meta['status'] ?? 'unknown') . '&message=' . urlencode($meta['message'] ?? ''); - return new RedirectResponse($redirectUrl); + // TODO: Determine how to send $dataResponse parameters to the caller or the redirect URL. + // For now, send them as querystring parameters for demonstration/debug purposes. + $redirectUrl .= '?status='.urlencode($meta['status'] ?? 'unknown').'&message='.urlencode($meta['message'] ?? ''); - // To return a JSON response to the caller (instead of redirecting): - /*return new JSONResponse([ - 'status' => $meta['status'] ?? 'unknown', - 'message' => $meta['message'] ?? '' - ], $status);*/ - } catch (Throwable $e) { - return new JSONResponse(['status' => 'error', 'message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); - } - } + return new RedirectResponse($redirectUrl); + // To return a JSON response to the caller (instead of redirecting): + /*return new JSONResponse([ + 'status' => $meta['status'] ?? 'unknown', + 'message' => $meta['message'] ?? '' + ], $status);*/ + } catch (Throwable $e) { + return new JSONResponse(['status' => 'error', 'message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); + } + } } diff --git a/lib/Controller/StorageController.php b/lib/Controller/StorageController.php index 4802eb7..46ecaf5 100644 --- a/lib/Controller/StorageController.php +++ b/lib/Controller/StorageController.php @@ -22,13 +22,14 @@ namespace OCA\Files_External_Ethswarm\Controller; +use Exception; use OCA\Files_External\Service\GlobalStoragesService; use OCA\Files_External_Ethswarm\AppInfo\Application; use OCA\Files_External_Ethswarm\Auth\AccessKey; use OCA\Files_External_Ethswarm\Backend\BeeSwarm; -use OCP\AppFramework\OCSController; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCSController; use OCP\IRequest; use OCP\IUserSession; use Psr\Log\LoggerInterface; @@ -52,11 +53,12 @@ public function __construct( } /** - * Create a new Hejbit Swarm external storage + * Create a new Hejbit Swarm external storage. * * @param string $folderName The folder name/mount point for the storage - * @param string $accessKey The Hejbit access key for authentication - * @param string $hostUrl The Access Server URL (e.g., "app.hejbit.com") + * @param string $accessKey The Hejbit access key for authentication + * @param string $hostUrl The Access Server URL (e.g., "app.hejbit.com") + * * @return DataResponse * @return DataResponse}}, array{}> * @return DataResponse}}, array{}> @@ -75,23 +77,23 @@ public function create( ): DataResponse { // Validate required parameters $validationError = $this->validateParameters($folderName, $accessKey, $hostUrl); - if ($validationError !== null) { + if (null !== $validationError) { return $validationError; } // Validate host URL format $validatedHost = $this->validateHostUrl($hostUrl); - if ($validatedHost === null) { + if (null === $validatedHost) { return $this->errorResponse('Invalid host URL format', 400); } // Ensure mount point starts with / - $mountPoint = '/' . ltrim($folderName, '/'); + $mountPoint = '/'.ltrim($folderName, '/'); try { // Get the current user $user = $this->userSession->getUser(); - if ($user === null) { + if (null === $user) { return $this->errorResponse('User not authenticated', 401); } @@ -101,7 +103,7 @@ public function create( Application::NAME, 'access:key', [BeeSwarm::OPTION_HOST_URL => $hostUrl, - AccessKey::SCHEME => $accessKey], + AccessKey::SCHEME => $accessKey], null, [] // Empty array = all users ); @@ -109,26 +111,26 @@ public function create( // Add the storage via the service $newStorage = $this->globalStoragesService->addStorage($storageConfig); - $this->logger->info('Swarm storage created successfully: ' . $mountPoint . ' for user: ' . $user->getUID()); + $this->logger->info('Swarm storage created successfully: '.$mountPoint.' for user: '.$user->getUID()); return $this->successResponse([ 'id' => $newStorage->getId(), 'mountPoint' => $newStorage->getMountPoint(), 'backend' => Application::NAME, ]); - } catch (\Exception $e) { - $this->logger->error('Failed to create Swarm storage: ' . $e->getMessage(), [ + } catch (Exception $e) { + $this->logger->error('Failed to create Swarm storage: '.$e->getMessage(), [ 'exception' => $e, 'folderName' => $folderName, - 'hostUrl' => $hostUrl + 'hostUrl' => $hostUrl, ]); - return $this->errorResponse('Failed to create storage: ' . $e->getMessage(), 500); + return $this->errorResponse('Failed to create storage: '.$e->getMessage(), 500); } } /** - * Validate required parameters + * Validate required parameters. */ private function validateParameters(string $folderName, string $accessKey, string $hostUrl): ?DataResponse { if (empty($folderName)) { @@ -140,23 +142,26 @@ private function validateParameters(string $folderName, string $accessKey, strin if (empty($hostUrl)) { return $this->errorResponse('Host URL is required', 400); } + return null; } /** - * Validate and normalize host URL - * @return string|null Normalized URL or null if invalid + * Validate and normalize host URL. + * + * @return null|string Normalized URL or null if invalid */ private function validateHostUrl(string $hostUrl): ?string { $validatedHost = $hostUrl; if (!preg_match('/^https?:\/\//i', $validatedHost)) { - $validatedHost = 'https://' . $validatedHost; + $validatedHost = 'https://'.$validatedHost; } + return filter_var($validatedHost, FILTER_VALIDATE_URL) ? $validatedHost : null; } /** - * Create a success response + * Create a success response. */ private function successResponse(array $data): DataResponse { return new DataResponse([ @@ -164,15 +169,15 @@ private function successResponse(array $data): DataResponse { 'meta' => [ 'status' => 'success', 'statuscode' => 201, - 'message' => 'Storage created successfully' + 'message' => 'Storage created successfully', ], - 'data' => $data - ] + 'data' => $data, + ], ], 201); } /** - * Create an error response + * Create an error response. */ private function errorResponse(string $message, int $statusCode): DataResponse { return new DataResponse([ @@ -180,10 +185,10 @@ private function errorResponse(string $message, int $statusCode): DataResponse { 'meta' => [ 'status' => 'failure', 'statuscode' => $statusCode, - 'message' => $message + 'message' => $message, ], - 'data' => [] - ] + 'data' => [], + ], ], $statusCode); } -} \ No newline at end of file +} From e89f88585bf6bc3ee6216ddabbf7e3f537b0403a Mon Sep 17 00:00:00 2001 From: Mahyar Iranibazaz Date: Mon, 23 Mar 2026 13:35:10 -0300 Subject: [PATCH 09/16] refactor: use const for the key on key --- lib/Auth/AccessKey.php | 3 ++- lib/Controller/StorageController.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Auth/AccessKey.php b/lib/Auth/AccessKey.php index 386bcda..55d271b 100755 --- a/lib/Auth/AccessKey.php +++ b/lib/Auth/AccessKey.php @@ -30,11 +30,12 @@ */ class AccessKey extends AuthMechanism { /** @const string */ + public const IDENTIFIER = 'access:key'; public const SCHEME = 'access_key'; public function __construct(IL10N $l) { $this - ->setIdentifier('access:key') + ->setIdentifier(self::IDENTIFIER) ->setScheme(self::SCHEME) ->setText($l->t('Access Key')) ->addParameters([ diff --git a/lib/Controller/StorageController.php b/lib/Controller/StorageController.php index 46ecaf5..f453a76 100644 --- a/lib/Controller/StorageController.php +++ b/lib/Controller/StorageController.php @@ -101,7 +101,7 @@ public function create( $storageConfig = $this->globalStoragesService->createStorage( $mountPoint, Application::NAME, - 'access:key', + AccessKey::IDENTIFIER, [BeeSwarm::OPTION_HOST_URL => $hostUrl, AccessKey::SCHEME => $accessKey], null, From 8eb760947b1d28b8d85a8d2944d577bcecdaa369 Mon Sep 17 00:00:00 2001 From: Mahyar Iranibazaz Date: Mon, 23 Mar 2026 13:36:55 -0300 Subject: [PATCH 10/16] chore: code style --- composer.json | 2 +- openapi.json | 826 ++++++++++++++++++++++++-------------------------- 2 files changed, 398 insertions(+), 430 deletions(-) diff --git a/composer.json b/composer.json index df12e18..6ea5d31 100644 --- a/composer.json +++ b/composer.json @@ -40,4 +40,4 @@ "forward-command": true } } -} \ No newline at end of file +} diff --git a/openapi.json b/openapi.json index 9a8e752..f33a580 100644 --- a/openapi.json +++ b/openapi.json @@ -1,431 +1,399 @@ { - "openapi": "3.0.3", - "info": { - "title": "files_external_ethswarm", - "version": "0.0.1", - "description": "Bring decentralized, sovereign cloud storage to Nextcloud with the HejBit Swarm plugin!", - "license": { - "name": "agpl" - } - }, - "components": { - "securitySchemes": { - "basic_auth": { - "type": "http", - "scheme": "basic" - }, - "bearer_auth": { - "type": "http", - "scheme": "bearer" - } - }, - "schemas": { - "OCSMeta": { - "type": "object", - "required": [ - "status", - "statuscode" - ], - "properties": { - "status": { - "type": "string" - }, - "statuscode": { - "type": "integer" - }, - "message": { - "type": "string" - }, - "totalitems": { - "type": "string" - }, - "itemsperpage": { - "type": "string" - } - } - } - } - }, - "paths": { - "/ocs/v2.php/apps/files_external_ethswarm/api/v1/storages": { - "post": { - "operationId": "storage-create", - "summary": "Create a new Hejbit Swarm external storage", - "tags": [ - "storage" - ], - "security": [ - { - "bearer_auth": [] - }, - { - "basic_auth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "folderName", - "accessKey", - "hostUrl" - ], - "properties": { - "folderName": { - "type": "string", - "description": "The folder name/mount point for the storage" - }, - "accessKey": { - "type": "string", - "description": "The Hejbit access key for authentication" - }, - "hostUrl": { - "type": "string", - "description": "The Access Server URL (e.g., \"app.hejbit.com\")" - } - } - } - } - } - }, - "parameters": [ - { - "name": "OCS-APIRequest", - "in": "header", - "description": "Required to be true for the API request to pass", - "required": true, - "schema": { - "type": "boolean", - "default": true - } - } - ], - "responses": { - "201": { - "description": "Storage created successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "type": "object", - "required": [ - "status", - "statuscode", - "message" - ], - "properties": { - "status": { - "type": "string" - }, - "statuscode": { - "type": "integer", - "format": "int64" - }, - "message": { - "type": "string" - } - } - }, - "data": { - "type": "object", - "required": [ - "id", - "mountPoint", - "backend" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "mountPoint": { - "type": "string" - }, - "backend": { - "type": "string" - } - } - } - } - } - } - } - } - } - } - } - } - } - }, - "400": { - "description": "Bad request (missing parameters or invalid URL)", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "type": "object", - "required": [ - "status", - "statuscode", - "message" - ], - "properties": { - "status": { - "type": "string" - }, - "statuscode": { - "type": "integer", - "format": "int64" - }, - "message": { - "type": "string" - } - } - }, - "data": { - "type": "object", - "additionalProperties": { - "type": "object" - } - } - } - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "Unauthorized (user not authenticated)", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "type": "object", - "required": [ - "status", - "statuscode", - "message" - ], - "properties": { - "status": { - "type": "string" - }, - "statuscode": { - "type": "integer", - "format": "int64" - }, - "message": { - "type": "string" - } - } - }, - "data": { - "type": "object", - "additionalProperties": { - "type": "object" - } - } - } - } - } - } - } - } - } - }, - { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": {} - } - } - } - } - ] - } - } - } - }, - "500": { - "description": "Internal server error (failed to create storage)", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "type": "object", - "required": [ - "status", - "statuscode", - "message" - ], - "properties": { - "status": { - "type": "string" - }, - "statuscode": { - "type": "integer", - "format": "int64" - }, - "message": { - "type": "string" - } - } - }, - "data": { - "type": "object", - "additionalProperties": { - "type": "object" - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - }, - "tags": [] + "openapi": "3.0.3", + "info": { + "title": "files_external_ethswarm", + "version": "0.0.1", + "description": "Bring decentralized, sovereign cloud storage to Nextcloud with the HejBit Swarm plugin!", + "license": { + "name": "agpl" + } + }, + "components": { + "securitySchemes": { + "basic_auth": { + "type": "http", + "scheme": "basic" + }, + "bearer_auth": { + "type": "http", + "scheme": "bearer" + } + }, + "schemas": { + "OCSMeta": { + "type": "object", + "required": ["status", "statuscode"], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "totalitems": { + "type": "string" + }, + "itemsperpage": { + "type": "string" + } + } + } + } + }, + "paths": { + "/ocs/v2.php/apps/files_external_ethswarm/api/v1/storages": { + "post": { + "operationId": "storage-create", + "summary": "Create a new Hejbit Swarm external storage", + "tags": ["storage"], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "folderName", + "accessKey", + "hostUrl" + ], + "properties": { + "folderName": { + "type": "string", + "description": "The folder name/mount point for the storage" + }, + "accessKey": { + "type": "string", + "description": "The Hejbit access key for authentication" + }, + "hostUrl": { + "type": "string", + "description": "The Access Server URL (e.g., \"app.hejbit.com\")" + } + } + } + } + } + }, + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "201": { + "description": "Storage created successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["ocs"], + "properties": { + "ocs": { + "type": "object", + "required": ["meta", "data"], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": ["ocs"], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "type": "object", + "required": [ + "status", + "statuscode", + "message" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + } + } + }, + "data": { + "type": "object", + "required": [ + "id", + "mountPoint", + "backend" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "mountPoint": { + "type": "string" + }, + "backend": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request (missing parameters or invalid URL)", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["ocs"], + "properties": { + "ocs": { + "type": "object", + "required": ["meta", "data"], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": ["ocs"], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "type": "object", + "required": [ + "status", + "statuscode", + "message" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + } + } + }, + "data": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized (user not authenticated)", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "object", + "required": ["ocs"], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": ["ocs"], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "type": "object", + "required": [ + "status", + "statuscode", + "message" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + } + } + }, + "data": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + } + }, + { + "type": "object", + "required": ["ocs"], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + ] + } + } + } + }, + "500": { + "description": "Internal server error (failed to create storage)", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["ocs"], + "properties": { + "ocs": { + "type": "object", + "required": ["meta", "data"], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": ["ocs"], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "type": "object", + "required": [ + "status", + "statuscode", + "message" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + } + } + }, + "data": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "tags": [] } From 52583411c19b9623f83058a090fbecad1305f772 Mon Sep 17 00:00:00 2001 From: Mahyar Iranibazaz Date: Mon, 23 Mar 2026 13:46:26 -0300 Subject: [PATCH 11/16] feat: reuse URL validation --- lib/Backend/BeeSwarm.php | 8 +++----- lib/Controller/StorageController.php | 27 ++++++++------------------- lib/Storage/BeeSwarmTrait.php | 15 ++++++++++----- lib/Utils/HostUrl.php | 23 +++++++++++++++++++++++ 4 files changed, 44 insertions(+), 29 deletions(-) create mode 100644 lib/Utils/HostUrl.php diff --git a/lib/Backend/BeeSwarm.php b/lib/Backend/BeeSwarm.php index fa49329..ca6f25b 100755 --- a/lib/Backend/BeeSwarm.php +++ b/lib/Backend/BeeSwarm.php @@ -27,6 +27,7 @@ use OCA\Files_External\Service\GlobalStoragesService; use OCA\Files_External_Ethswarm\AppInfo\Application; use OCA\Files_External_Ethswarm\Auth\AccessKey; +use OCA\Files_External_Ethswarm\Utils\HostUrl; use OCP\IConfig; use OCP\IL10N; use Psr\Log\LoggerInterface; @@ -76,11 +77,8 @@ public function validateStorageDefinition(StorageConfig $storage): bool { } // server url - $host = $storage->getBackendOption(self::OPTION_HOST_URL); - if (!preg_match('/^https?:\/\//i', $host)) { - $host = 'https://'.$host; - } - if (!filter_var($host, FILTER_VALIDATE_URL)) { + $host = HostUrl::normalize((string) $storage->getBackendOption(self::OPTION_HOST_URL)); + if (null === $host) { $this->logger->warning('invalid url'); $result = false; } diff --git a/lib/Controller/StorageController.php b/lib/Controller/StorageController.php index f453a76..3e63c34 100644 --- a/lib/Controller/StorageController.php +++ b/lib/Controller/StorageController.php @@ -27,6 +27,7 @@ use OCA\Files_External_Ethswarm\AppInfo\Application; use OCA\Files_External_Ethswarm\Auth\AccessKey; use OCA\Files_External_Ethswarm\Backend\BeeSwarm; +use OCA\Files_External_Ethswarm\Utils\HostUrl; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCSController; @@ -82,7 +83,7 @@ public function create( } // Validate host URL format - $validatedHost = $this->validateHostUrl($hostUrl); + $validatedHost = HostUrl::normalize($hostUrl); if (null === $validatedHost) { return $this->errorResponse('Invalid host URL format', 400); } @@ -102,10 +103,12 @@ public function create( $mountPoint, Application::NAME, AccessKey::IDENTIFIER, - [BeeSwarm::OPTION_HOST_URL => $hostUrl, - AccessKey::SCHEME => $accessKey], + [ + BeeSwarm::OPTION_HOST_URL => $validatedHost, + AccessKey::SCHEME => $accessKey + ], null, - [] // Empty array = all users + [], // Empty array = all users ); // Add the storage via the service @@ -122,7 +125,7 @@ public function create( $this->logger->error('Failed to create Swarm storage: '.$e->getMessage(), [ 'exception' => $e, 'folderName' => $folderName, - 'hostUrl' => $hostUrl, + 'hostUrl' => $validatedHost, ]); return $this->errorResponse('Failed to create storage: '.$e->getMessage(), 500); @@ -146,20 +149,6 @@ private function validateParameters(string $folderName, string $accessKey, strin return null; } - /** - * Validate and normalize host URL. - * - * @return null|string Normalized URL or null if invalid - */ - private function validateHostUrl(string $hostUrl): ?string { - $validatedHost = $hostUrl; - if (!preg_match('/^https?:\/\//i', $validatedHost)) { - $validatedHost = 'https://'.$validatedHost; - } - - return filter_var($validatedHost, FILTER_VALIDATE_URL) ? $validatedHost : null; - } - /** * Create a success response. */ diff --git a/lib/Storage/BeeSwarmTrait.php b/lib/Storage/BeeSwarmTrait.php index 85c96ea..60a8a13 100755 --- a/lib/Storage/BeeSwarmTrait.php +++ b/lib/Storage/BeeSwarmTrait.php @@ -28,6 +28,7 @@ use OCA\Files_External_Ethswarm\Exception\CurlException; use OCA\Files_External_Ethswarm\Exception\HejBitException; use OCA\Files_External_Ethswarm\Utils\Curl; +use OCA\Files_External_Ethswarm\Utils\HostUrl; use OCP\Files\StorageBadConfigException; use OCP\Files\StorageNotAvailableException; @@ -61,15 +62,19 @@ protected function parseParams(array $params): void { * @throws StorageBadConfigException */ private function validateParams(array &$params): void { - if (!$params[BeeSwarm::OPTION_HOST_URL] || !$params[AccessKey::SCHEME]) { + $hostUrl = (string) ($params[BeeSwarm::OPTION_HOST_URL] ?? ''); + $accessKey = (string) ($params[AccessKey::SCHEME] ?? ''); + + if (empty($hostUrl) || empty($accessKey)) { throw new StorageBadConfigException('Creating '.self::class.' storage failed, required parameters not set'); } - if (!preg_match('/^https?:\/\//i', $params[BeeSwarm::OPTION_HOST_URL])) { - $params[BeeSwarm::OPTION_HOST_URL] = 'https://'.$params[BeeSwarm::OPTION_HOST_URL]; - } - if (!filter_var($params[BeeSwarm::OPTION_HOST_URL], FILTER_VALIDATE_URL)) { + + $validatedHostUrl = HostUrl::normalize($hostUrl); + if (null === $validatedHostUrl) { throw new StorageBadConfigException('Creating '.self::class.' storage failed, invalid url'); } + + $params[BeeSwarm::OPTION_HOST_URL] = $validatedHostUrl; } /** diff --git a/lib/Utils/HostUrl.php b/lib/Utils/HostUrl.php new file mode 100644 index 0000000..7b795b5 --- /dev/null +++ b/lib/Utils/HostUrl.php @@ -0,0 +1,23 @@ + Date: Mon, 23 Mar 2026 13:46:39 -0300 Subject: [PATCH 12/16] chore: code style --- lib/Controller/StorageController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Controller/StorageController.php b/lib/Controller/StorageController.php index 3e63c34..e43aaa9 100644 --- a/lib/Controller/StorageController.php +++ b/lib/Controller/StorageController.php @@ -105,7 +105,7 @@ public function create( AccessKey::IDENTIFIER, [ BeeSwarm::OPTION_HOST_URL => $validatedHost, - AccessKey::SCHEME => $accessKey + AccessKey::SCHEME => $accessKey, ], null, [], // Empty array = all users From 680dbcd7ad942ba1103ffa57b162a32e6c020ebe Mon Sep 17 00:00:00 2001 From: Mahyar Iranibazaz Date: Mon, 23 Mar 2026 14:30:35 -0300 Subject: [PATCH 13/16] feat: error and creation handling --- lib/Controller/ConfigurationController.php | 42 ++++---- lib/Controller/StorageController.php | 118 ++++++++++++++++++--- 2 files changed, 129 insertions(+), 31 deletions(-) diff --git a/lib/Controller/ConfigurationController.php b/lib/Controller/ConfigurationController.php index e31cee1..085b9be 100644 --- a/lib/Controller/ConfigurationController.php +++ b/lib/Controller/ConfigurationController.php @@ -7,8 +7,8 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; -use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\Http\TemplateResponse; use OCP\IRequest; use OCP\IURLGenerator; use Throwable; @@ -30,39 +30,45 @@ public function __construct( /** * Create a storage definition by delegating to StorageController::create(). - * - * @return JSONResponse|RedirectResponse JSON response with status and message, or redirect */ #[NoCSRFRequired] - public function create(): JSONResponse|RedirectResponse { + public function create(): RedirectResponse|TemplateResponse { $params = $this->request->getParams(); - $key = trim($params['key'] ?? ''); - $folder = trim($params['folder'] ?? 'Hejbit-Storage'); - $hosturl = trim($params['hosturl'] ?? 'app.hejbit.com'); + $accessKey = trim((string) ($params['accessKey'] ?? '')); + $folderName = trim((string) ($params['folderName'] ?? 'Hejbit-Storage')); + $hostUrl = trim((string) ($params['hostUrl'] ?? 'app.hejbit.com')); try { - $dataResponse = $this->storageController->create($folder, $key, $hosturl); + $dataResponse = $this->storageController->create($folderName, $accessKey, $hostUrl); // Extract status and message from DataResponse $data = $dataResponse->getData(); $meta = $data['ocs']['meta'] ?? []; $status = $dataResponse->getStatus(); + $isFailure = Http::STATUS_CREATED !== $status || 'success' !== ($meta['status'] ?? ''); + + if ($isFailure) { + return $this->buildErrorResponse((string) ($meta['message'] ?? 'Failed to create storage'), $status); + } // Redirect to NC external storage mounts page after creation $redirectUrl = $this->urlGenerator->getAbsoluteURL('/apps/files/extstoragemounts'); - // TODO: Determine how to send $dataResponse parameters to the caller or the redirect URL. - // For now, send them as querystring parameters for demonstration/debug purposes. - $redirectUrl .= '?status='.urlencode($meta['status'] ?? 'unknown').'&message='.urlencode($meta['message'] ?? ''); - return new RedirectResponse($redirectUrl); - // To return a JSON response to the caller (instead of redirecting): - /*return new JSONResponse([ - 'status' => $meta['status'] ?? 'unknown', - 'message' => $meta['message'] ?? '' - ], $status);*/ } catch (Throwable $e) { - return new JSONResponse(['status' => 'error', 'message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); + return $this->buildErrorResponse($e->getMessage(), Http::STATUS_INTERNAL_SERVER_ERROR); } } + + private function buildErrorResponse(string $message, int $statusCode): TemplateResponse { + $response = new TemplateResponse('core', 'error', [ + 'errors' => [ + ['error' => $message], + ], + ], 'error'); + + $response->setStatus($statusCode); + + return $response; + } } diff --git a/lib/Controller/StorageController.php b/lib/Controller/StorageController.php index e43aaa9..a256913 100644 --- a/lib/Controller/StorageController.php +++ b/lib/Controller/StorageController.php @@ -23,6 +23,9 @@ namespace OCA\Files_External_Ethswarm\Controller; use Exception; +use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; +use OCA\Files_External\Lib\StorageConfig; +use OCA\Files_External\MountConfig; use OCA\Files_External\Service\GlobalStoragesService; use OCA\Files_External_Ethswarm\AppInfo\Application; use OCA\Files_External_Ethswarm\Auth\AccessKey; @@ -31,6 +34,7 @@ use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCSController; +use OCP\Files\StorageNotAvailableException; use OCP\IRequest; use OCP\IUserSession; use Psr\Log\LoggerInterface; @@ -77,19 +81,22 @@ public function create( string $hostUrl ): DataResponse { // Validate required parameters - $validationError = $this->validateParameters($folderName, $accessKey, $hostUrl); - if (null !== $validationError) { - return $validationError; + $validationErrors = $this->validateParameters($folderName, $accessKey, $hostUrl); + if (!empty($validationErrors)) { + return $this->validationErrorResponse($validationErrors); } // Validate host URL format $validatedHost = HostUrl::normalize($hostUrl); if (null === $validatedHost) { - return $this->errorResponse('Invalid host URL format', 400); + return $this->validationErrorResponse([ + 'hostUrl' => 'Invalid host URL format', + ]); } // Ensure mount point starts with / $mountPoint = '/'.ltrim($folderName, '/'); + $mountPoint = $this->resolveUniqueMountPoint($mountPoint); try { // Get the current user @@ -111,6 +118,19 @@ public function create( [], // Empty array = all users ); + $connectionValidationError = $this->validateStorageConnection($storageConfig); + if (null !== $connectionValidationError) { + return $this->errorResponse( + 'Failed to connect to external storage: '.$connectionValidationError, + 400, + [ + 'errors' => [ + 'connection' => $connectionValidationError, + ], + ] + ); + } + // Add the storage via the service $newStorage = $this->globalStoragesService->addStorage($storageConfig); @@ -134,16 +154,88 @@ public function create( /** * Validate required parameters. + * + * @return array */ - private function validateParameters(string $folderName, string $accessKey, string $hostUrl): ?DataResponse { - if (empty($folderName)) { - return $this->errorResponse('Folder name is required', 400); + private function validateParameters(string $folderName, string $accessKey, string $hostUrl): array { + $errors = []; + + if (empty(trim($folderName))) { + $errors['folderName'] = 'Folder name is required'; } - if (empty($accessKey)) { - return $this->errorResponse('Access key is required', 400); + + if (empty(trim($accessKey))) { + $errors['accessKey'] = 'Access key is required'; + } + + if (empty(trim($hostUrl))) { + $errors['hostUrl'] = 'Host URL is required'; } - if (empty($hostUrl)) { - return $this->errorResponse('Host URL is required', 400); + + return $errors; + } + + /** + * Create a validation error response with an errors bag. + * + * @param array $errors + */ + private function validationErrorResponse(array $errors): DataResponse { + return $this->errorResponse( + implode('; ', array_values($errors)), + 400, + ['errors' => $errors] + ); + } + + /** + * Resolve a unique mount point by appending a numeric suffix when needed. + */ + private function resolveUniqueMountPoint(string $mountPoint): string { + $existingMountPoints = []; + foreach ($this->globalStoragesService->getAllGlobalStorages() as $storage) { + $existingMountPoints[strtolower($storage->getMountPoint())] = true; + } + + if (!isset($existingMountPoints[strtolower($mountPoint)])) { + return $mountPoint; + } + + $baseMountPoint = $mountPoint; + $suffix = 1; + do { + $candidateMountPoint = $baseMountPoint.'-'.$suffix; + ++$suffix; + } while (isset($existingMountPoints[strtolower($candidateMountPoint)])); + + return $candidateMountPoint; + } + + /** + * Validate storage connectivity before persisting config. + */ + private function validateStorageConnection(StorageConfig $storageConfig): ?string { + try { + $authMechanism = $storageConfig->getAuthMechanism(); + $authMechanism->manipulateStorageConfig($storageConfig); + + $backend = $storageConfig->getBackend(); + $backend->manipulateStorageConfig($storageConfig); + + $status = MountConfig::getBackendStatus( + $backend->getStorageClass(), + $storageConfig->getBackendOptions(), + ); + + if (StorageNotAvailableException::STATUS_SUCCESS !== $status) { + return StorageNotAvailableException::getStateCodeName($status); + } + } catch (InsufficientDataForMeaningfulAnswerException $e) { + return 'Insufficient data: '.$e->getMessage(); + } catch (StorageNotAvailableException $e) { + return $e->getMessage(); + } catch (Exception $e) { + return $e->getMessage(); } return null; @@ -168,7 +260,7 @@ private function successResponse(array $data): DataResponse { /** * Create an error response. */ - private function errorResponse(string $message, int $statusCode): DataResponse { + private function errorResponse(string $message, int $statusCode, array $data = []): DataResponse { return new DataResponse([ 'ocs' => [ 'meta' => [ @@ -176,7 +268,7 @@ private function errorResponse(string $message, int $statusCode): DataResponse { 'statuscode' => $statusCode, 'message' => $message, ], - 'data' => [], + 'data' => $data, ], ], $statusCode); } From 3eab3a7c128015027a1fdb5879834dbda1004116 Mon Sep 17 00:00:00 2001 From: Mahyar Iranibazaz Date: Mon, 23 Mar 2026 14:56:32 -0300 Subject: [PATCH 14/16] feat: lint-stage and precommit rules --- .github/workflows/lint.yml | 54 +++++++--- package.json | 14 ++- pnpm-lock.yaml | 210 +++++++++++++++++++++++++++++++++++++ 3 files changed, 262 insertions(+), 16 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 59c9640..d079361 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,5 +1,8 @@ name: Lint +permissions: + contents: write + on: pull_request: branches: @@ -10,16 +13,16 @@ on: - reopened jobs: - backend-lint: - name: Backend Lint + format-sync: + name: Format Sync runs-on: ubuntu-latest - strategy: - fail-fast: true - steps: - name: Checkout code uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -27,19 +30,49 @@ jobs: php-version: "8.3" tools: composer - - name: Install php-cs-fixer - run: composer require --dev friendsofphp/php-cs-fixer + - name: Install PHP dependencies + run: composer install --no-interaction --prefer-dist + + - name: Apply php-cs-fixer + run: vendor/bin/php-cs-fixer fix + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.28.2 + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + cache: "pnpm" - - name: Run php-cs-fixer - run: vendor/bin/php-cs-fixer fix --dry-run --diff + - name: Install JS dependencies + run: pnpm install --frozen-lockfile + + - name: Apply Biome format + run: pnpm run format:fix + + - name: Commit format changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore: apply automated formatting" + commit_user_name: "github-actions[bot]" + commit_user_email: "github-actions[bot]@users.noreply.github.com" + commit_author: "github-actions[bot] " frontend-lint: name: Frontend Lint runs-on: ubuntu-latest + needs: format-sync steps: - name: Checkout code uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 - name: Setup pnpm uses: pnpm/action-setup@v4 @@ -59,8 +92,5 @@ jobs: - name: Run Biome lint run: pnpm run lint - - name: Run Biome format check - run: pnpm run format - - name: Run TypeScript type check run: pnpm run typecheck diff --git a/package.json b/package.json index 60b2177..b106d7b 100644 --- a/package.json +++ b/package.json @@ -17,15 +17,16 @@ "dev": "vite build --mode development --watch", "serve": "vite --mode development --host 127.0.0.1", "check": "pnpm run format && pnpm run lint && pnpm run typecheck", - "precommit": "pnpm run format:fix && pnpm run backend:format:fix && pnpm run lint && pnpm run typecheck", + "prepare": "pnpm run prepare:format && pnpm run prepare:check", + "prepare:format": "lint-staged", + "prepare:check": "pnpm run lint && pnpm run typecheck", "lint": "biome lint src styles", "lint:fix": "biome lint --write src styles", "format": "biome format .", "format:fix": "biome format --write .", "backend:format": "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --dry-run", "backend:format:fix": "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix", - "typecheck": "vue-tsc --noEmit", - "prepare": "simple-git-hooks" + "typecheck": "vue-tsc --noEmit" }, "dependencies": { "@betahuhn/feedback-js": "^2.1.25", @@ -48,6 +49,7 @@ "@nextcloud/vite-config": "^2.5.2", "@types/node": "^24.5.2", "browserslist": "^4.26.2", + "lint-staged": "^16.2.7", "simple-git-hooks": "^2.13.1", "typescript": "^5.9.3", "vite": "^7.3.1", @@ -57,6 +59,10 @@ "extends @nextcloud/browserslist-config" ], "simple-git-hooks": { - "pre-commit": "pnpm run precommit" + "pre-commit": "pnpm run prepare" + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx,vue,json,css,scss,md}": "biome format --write", + "**/*.php": "pnpm run backend:format:fix --" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 295b2d4..ce872a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,6 +63,9 @@ importers: browserslist: specifier: ^4.26.2 version: 4.28.1 + lint-staged: + specifier: ^16.2.7 + version: 16.4.0 simple-git-hooks: specifier: ^2.13.1 version: 2.13.1 @@ -1121,6 +1124,18 @@ packages: alien-signals@3.1.2: resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -1290,10 +1305,21 @@ packages: resolution: {integrity: sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==} engines: {node: '>= 0.10'} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} + clone@2.1.2: resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} engines: {node: '>=0.8'} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1301,6 +1327,10 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + commenting@1.1.0: resolution: {integrity: sha512-YeNK4tavZwtH7jEgK1ZINXzLKm6DZdEMfsaaieOsCAN0S8vsY7UeuO3Q7d/M018EFgE+IeUAuBOKkFccBZsUZA==} @@ -1431,6 +1461,9 @@ packages: peerDependencies: vue: '>2.0.0' + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -1439,6 +1472,10 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1580,6 +1617,10 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1705,6 +1746,10 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + is-generator-function@1.1.2: resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} engines: {node: '>= 0.4'} @@ -1774,6 +1819,15 @@ packages: linkifyjs@4.3.2: resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==} + lint-staged@16.4.0: + resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==} + engines: {node: '>=20.17'} + hasBin: true + + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} + local-pkg@1.1.2: resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} engines: {node: '>=14'} @@ -1785,6 +1839,10 @@ packages: lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -1927,6 +1985,10 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -1996,6 +2058,10 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + os-browserify@0.3.0: resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} @@ -2171,6 +2237,13 @@ packages: engines: {node: '>= 0.4'} hasBin: true + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + ripemd160@2.0.3: resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==} engines: {node: '>= 0.8'} @@ -2264,10 +2337,22 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + simple-git-hooks@2.13.1: resolution: {integrity: sha512-WszCLXwT4h2k1ufIXAgsbiTOazqqevFCIncOuUBZJ91DdvWcC5+OFkluWRQPrcuSYd8fjq+o2y1QfWqYMoAToQ==} hasBin: true + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2321,6 +2406,14 @@ packages: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.2.0: + resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} + engines: {node: '>=20'} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -2330,6 +2423,10 @@ packages: stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -2365,6 +2462,10 @@ packages: resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==} engines: {node: '>=0.6.0'} + tinyexec@1.0.4: + resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -2599,6 +2700,10 @@ packages: resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} engines: {node: '>= 0.4'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -3573,6 +3678,14 @@ snapshots: alien-signals@3.1.2: {} + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@6.2.2: {} + + ansi-styles@6.2.3: {} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -3767,14 +3880,27 @@ snapshots: safe-buffer: 5.2.1 to-buffer: 1.2.2 + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@5.2.0: + dependencies: + slice-ansi: 8.0.0 + string-width: 8.2.0 + clone@2.1.2: {} + colorette@2.0.20: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 comma-separated-tokens@2.0.3: {} + commander@14.0.3: {} + commenting@1.1.0: {} compare-versions@6.1.1: {} @@ -3920,10 +4046,14 @@ snapshots: core-js: 3.49.0 vue: 3.5.30(typescript@5.9.3) + emoji-regex@10.6.0: {} + entities@6.0.1: {} entities@7.0.1: {} + environment@1.1.0: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -4094,6 +4224,8 @@ snapshots: generator-function@2.0.1: {} + get-east-asian-width@1.5.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4237,6 +4369,10 @@ snapshots: is-extglob@2.1.1: optional: true + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.5.0 + is-generator-function@1.1.2: dependencies: call-bound: 1.0.4 @@ -4300,6 +4436,24 @@ snapshots: linkifyjs@4.3.2: {} + lint-staged@16.4.0: + dependencies: + commander: 14.0.3 + listr2: 9.0.5 + picomatch: 4.0.3 + string-argv: 0.3.2 + tinyexec: 1.0.4 + yaml: 2.8.2 + + listr2@9.0.5: + dependencies: + cli-truncate: 5.2.0 + colorette: 2.0.20 + eventemitter3: 5.0.4 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + local-pkg@1.1.2: dependencies: mlly: 1.8.1 @@ -4312,6 +4466,14 @@ snapshots: lodash@4.17.23: {} + log-update@6.1.0: + dependencies: + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + longest-streak@3.1.0: {} lowlight@3.3.0: @@ -4600,6 +4762,8 @@ snapshots: dependencies: mime-db: 1.52.0 + mimic-function@5.0.1: {} + minimalistic-assert@1.0.1: {} minimalistic-crypto-utils@1.0.1: {} @@ -4690,6 +4854,10 @@ snapshots: has-symbols: 1.1.0 object-keys: 1.1.1 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + os-browserify@0.3.0: {} p-limit@3.1.0: @@ -4908,6 +5076,13 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + rfdc@1.4.1: {} + ripemd160@2.0.3: dependencies: hash-base: 3.1.2 @@ -5049,8 +5224,20 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + signal-exit@4.1.0: {} + simple-git-hooks@2.13.1: {} + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + slice-ansi@8.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + source-map-js@1.2.1: {} source-map@0.6.1: {} @@ -5109,6 +5296,17 @@ snapshots: string-argv@0.3.2: {} + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + + string-width@8.2.0: + dependencies: + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -5122,6 +5320,10 @@ snapshots: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + strip-json-comments@3.1.1: {} striptags@3.2.0: {} @@ -5152,6 +5354,8 @@ snapshots: dependencies: setimmediate: 1.0.5 + tinyexec@1.0.4: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -5411,6 +5615,12 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + xtend@4.0.2: {} yallist@4.0.0: {} From 5bc7a4cc2537774e9623fef6943a2f06c7bcf43f Mon Sep 17 00:00:00 2001 From: Mahyar Iranibazaz Date: Mon, 23 Mar 2026 15:03:32 -0300 Subject: [PATCH 15/16] fix: lint ci --- .github/workflows/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d079361..77f835b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,8 +30,8 @@ jobs: php-version: "8.3" tools: composer - - name: Install PHP dependencies - run: composer install --no-interaction --prefer-dist + - name: Install php-cs-fixer + run: composer require --dev friendsofphp/php-cs-fixer nextcloud/coding-standard --no-interaction --no-progress --no-scripts - name: Apply php-cs-fixer run: vendor/bin/php-cs-fixer fix From 6da78db27af59fe77265da351f25ff40f3621ef9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Mar 2026 18:04:27 +0000 Subject: [PATCH 16/16] chore: apply automated formatting --- composer.json | 2 +- composer.lock | 3510 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 3421 insertions(+), 91 deletions(-) diff --git a/composer.json b/composer.json index 6ea5d31..9b92b74 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "ext-simplexml": "*" }, "require-dev": { - "nextcloud/coding-standard": "^1.3.2", + "nextcloud/coding-standard": "^1.4", "ext-fileinfo": "*", "friendsofphp/php-cs-fixer": "*", "bamarni/composer-bin-plugin": "^1.8", diff --git a/composer.lock b/composer.lock index 9e37ca0..daad878 100644 --- a/composer.lock +++ b/composer.lock @@ -4,94 +4,3131 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5ec978b13df1bcd971c7c9d0cd723f22", + "content-hash": "3cbbabee6f54b45e105251efa4eff7f8", "packages": [], "packages-dev": [ + { + "name": "amphp/amp", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/fa0ab33a6f47a82929c38d03ca47ebb71086a93f", + "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Future/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-08-27T21:42:00+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/parser": "^1.1", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2.3" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.22.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "https://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v2.1.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-03-16T17:10:27+00:00" + }, + { + "name": "amphp/cache", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/cache.git", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/cache/zipball/46912e387e6aa94933b61ea1ead9cf7540b7797c", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Cache\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + } + ], + "description": "A fiber-aware cache API based on Amp and Revolt.", + "homepage": "https://amphp.org/cache", + "support": { + "issues": "https://github.com/amphp/cache/issues", + "source": "https://github.com/amphp/cache/tree/v2.0.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:38:06+00:00" + }, + { + "name": "amphp/dns", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/dns.git", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/dns/zipball/78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/process": "^2", + "daverandom/libdns": "^2.0.2", + "ext-filter": "*", + "ext-json": "*", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Dns\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Wright", + "email": "addr@daverandom.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "Async DNS resolution for Amp.", + "homepage": "https://github.com/amphp/dns", + "keywords": [ + "amp", + "amphp", + "async", + "client", + "dns", + "resolve" + ], + "support": { + "issues": "https://github.com/amphp/dns/issues", + "source": "https://github.com/amphp/dns/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-01-19T15:43:40+00:00" + }, + { + "name": "amphp/parallel", + "version": "v2.3.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/parallel.git", + "reference": "296b521137a54d3a02425b464e5aee4c93db2c60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parallel/zipball/296b521137a54d3a02425b464e5aee4c93db2c60", + "reference": "296b521137a54d3a02425b464e5aee4c93db2c60", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/pipeline": "^1", + "amphp/process": "^2", + "amphp/serialization": "^1", + "amphp/socket": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.18" + }, + "type": "library", + "autoload": { + "files": [ + "src/Context/functions.php", + "src/Context/Internal/functions.php", + "src/Ipc/functions.php", + "src/Worker/functions.php" + ], + "psr-4": { + "Amp\\Parallel\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Parallel processing component for Amp.", + "homepage": "https://github.com/amphp/parallel", + "keywords": [ + "async", + "asynchronous", + "concurrent", + "multi-processing", + "multi-threading" + ], + "support": { + "issues": "https://github.com/amphp/parallel/issues", + "source": "https://github.com/amphp/parallel/tree/v2.3.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-11-15T06:23:42+00:00" + }, + { + "name": "amphp/parser", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/parser.git", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "shasum": "" + }, + "require": { + "php": ">=7.4" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Parser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A generator parser to make streaming parsers simple.", + "homepage": "https://github.com/amphp/parser", + "keywords": [ + "async", + "non-blocking", + "parser", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/parser/issues", + "source": "https://github.com/amphp/parser/tree/v1.1.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-21T19:16:53+00:00" + }, + { + "name": "amphp/pipeline", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/pipeline.git", + "reference": "7b52598c2e9105ebcddf247fc523161581930367" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/7b52598c2e9105ebcddf247fc523161581930367", + "reference": "7b52598c2e9105ebcddf247fc523161581930367", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "php": ">=8.1", + "revolt/event-loop": "^1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.18" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Pipeline\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Asynchronous iterators and operators.", + "homepage": "https://amphp.org/pipeline", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "iterator", + "non-blocking" + ], + "support": { + "issues": "https://github.com/amphp/pipeline/issues", + "source": "https://github.com/amphp/pipeline/tree/v1.2.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-03-16T16:33:53+00:00" + }, + { + "name": "amphp/process", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/process.git", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/process/zipball/52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Process\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A fiber-aware process manager based on Amp and Revolt.", + "homepage": "https://amphp.org/process", + "support": { + "issues": "https://github.com/amphp/process/issues", + "source": "https://github.com/amphp/process/tree/v2.0.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:13:44+00:00" + }, + { + "name": "amphp/serialization", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/serialization.git", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "phpunit/phpunit": "^9 || ^8 || ^7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Serialization\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Serialization tools for IPC and data storage in PHP.", + "homepage": "https://github.com/amphp/serialization", + "keywords": [ + "async", + "asynchronous", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/amphp/serialization/issues", + "source": "https://github.com/amphp/serialization/tree/master" + }, + "time": "2020-03-25T21:39:07+00:00" + }, + { + "name": "amphp/socket", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/socket.git", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/socket/zipball/58e0422221825b79681b72c50c47a930be7bf1e1", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/dns": "^2", + "ext-openssl": "*", + "kelunik/certificate": "^1.1", + "league/uri": "^6.5 | ^7", + "league/uri-interfaces": "^2.3 | ^7", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "amphp/process": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php", + "src/SocketAddress/functions.php" + ], + "psr-4": { + "Amp\\Socket\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@gmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Non-blocking socket connection / server implementations based on Amp and Revolt.", + "homepage": "https://github.com/amphp/socket", + "keywords": [ + "amp", + "async", + "encryption", + "non-blocking", + "sockets", + "tcp", + "tls" + ], + "support": { + "issues": "https://github.com/amphp/socket/issues", + "source": "https://github.com/amphp/socket/tree/v2.3.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-21T14:33:03+00:00" + }, + { + "name": "amphp/sync", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/sync.git", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/sync/zipball/217097b785130d77cfcc58ff583cf26cd1770bf1", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Sync\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Non-blocking synchronization primitives for PHP based on Amp and Revolt.", + "homepage": "https://github.com/amphp/sync", + "keywords": [ + "async", + "asynchronous", + "mutex", + "semaphore", + "synchronization" + ], + "support": { + "issues": "https://github.com/amphp/sync/issues", + "source": "https://github.com/amphp/sync/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-08-03T19:31:26+00:00" + }, { "name": "bamarni/composer-bin-plugin", "version": "1.8.2", "source": { "type": "git", - "url": "https://github.com/bamarni/composer-bin-plugin.git", - "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880" + "url": "https://github.com/bamarni/composer-bin-plugin.git", + "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880", + "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "ext-json": "*", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.5", + "symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", + "symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", + "symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Bamarni\\Composer\\Bin\\BamarniBinPlugin" + }, + "autoload": { + "psr-4": { + "Bamarni\\Composer\\Bin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "No conflicts for your bin dependencies", + "keywords": [ + "composer", + "conflict", + "dependency", + "executable", + "isolation", + "tool" + ], + "support": { + "issues": "https://github.com/bamarni/composer-bin-plugin/issues", + "source": "https://github.com/bamarni/composer-bin-plugin/tree/1.8.2" + }, + "time": "2022-10-31T08:38:03+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "danog/advanced-json-rpc", + "version": "v3.2.3", + "source": { + "type": "git", + "url": "https://github.com/danog/php-advanced-json-rpc.git", + "reference": "ae703ea7b4811797a10590b6078de05b3b33dd91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/danog/php-advanced-json-rpc/zipball/ae703ea7b4811797a10590b6078de05b3b33dd91", + "reference": "ae703ea7b4811797a10590b6078de05b3b33dd91", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^5", + "php": ">=8.1", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0 || ^6" + }, + "replace": { + "felixfbecker/php-advanced-json-rpc": "^3" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + }, + { + "name": "Daniil Gentili", + "email": "daniil@daniil.it" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/danog/php-advanced-json-rpc/issues", + "source": "https://github.com/danog/php-advanced-json-rpc/tree/v3.2.3" + }, + "time": "2026-01-12T21:07:10+00:00" + }, + { + "name": "daverandom/libdns", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/DaveRandom/LibDNS.git", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "Required for IDN support" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "LibDNS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "DNS protocol implementation written in pure PHP", + "keywords": [ + "dns" + ], + "support": { + "issues": "https://github.com/DaveRandom/LibDNS/issues", + "source": "https://github.com/DaveRandom/LibDNS/tree/v2.1.0" + }, + "time": "2024-04-12T12:12:48+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.6", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "time": "2026-02-07T07:09:04+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.3", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.3" + }, + "time": "2024-04-30T00:40:11+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, + { + "name": "kelunik/certificate", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/kelunik/certificate.git", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kelunik/certificate/zipball/7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">=7.0" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^6 | 7 | ^8 | ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Kelunik\\Certificate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Access certificate details and transform between different formats.", + "keywords": [ + "DER", + "certificate", + "certificates", + "openssl", + "pem", + "x509" + ], + "support": { + "issues": "https://github.com/kelunik/certificate/issues", + "source": "https://github.com/kelunik/certificate/tree/v1.1.3" + }, + "time": "2023-02-03T21:26:53+00:00" + }, + { + "name": "kubawerlos/php-cs-fixer-custom-fixers", + "version": "v3.22.0", + "source": { + "type": "git", + "url": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers.git", + "reference": "8701394f0c7cd450ac4fa577d24589122c1d5d5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kubawerlos/php-cs-fixer-custom-fixers/zipball/8701394f0c7cd450ac4fa577d24589122c1d5d5e", + "reference": "8701394f0c7cd450ac4fa577d24589122c1d5d5e", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-tokenizer": "*", + "friendsofphp/php-cs-fixer": "^3.61.1", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6.4 || ^10.5.29" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpCsFixerCustomFixers\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kuba Werłos", + "email": "werlos@gmail.com" + } + ], + "description": "A set of custom fixers for PHP CS Fixer", + "support": { + "issues": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/issues", + "source": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/tree/v3.22.0" + }, + "time": "2024-08-16T20:44:35+00:00" + }, + { + "name": "league/uri", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/08cf38e3924d4f56238125547b5720496fac8fd4", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.8.1", + "php": "^8.1", + "psr/http-factory": "^1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "URN", + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc2141", + "rfc3986", + "rfc3987", + "rfc6570", + "rfc8141", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-15T20:22:25+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/85d5c77c5d6d3af6c54db4a78246364908f3c928", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-08T20:05:35+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v5.0.1", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "980674efdda65913492d29a8fd51c82270dd37bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/980674efdda65913492d29a8fd51c82270dd37bb", + "reference": "980674efdda65913492d29a8fd51c82270dd37bb", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v5.0.1" + }, + "time": "2026-02-22T16:28:03+00:00" + }, + { + "name": "nextcloud/coding-standard", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/nextcloud/coding-standard.git", + "reference": "8e06808c1423e9208d63d1bd205b9a38bd400011" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nextcloud/coding-standard/zipball/8e06808c1423e9208d63d1bd205b9a38bd400011", + "reference": "8e06808c1423e9208d63d1bd205b9a38bd400011", + "shasum": "" + }, + "require": { + "kubawerlos/php-cs-fixer-custom-fixers": "^3.22", + "php": "^8.0", + "php-cs-fixer/shim": "^3.17" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nextcloud\\CodingStandard\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christoph Wurst", + "email": "christoph@winzerhof-wurst.at" + } + ], + "description": "Nextcloud coding standards for the php cs fixer", + "keywords": [ + "dev" + ], + "support": { + "issues": "https://github.com/nextcloud/coding-standard/issues", + "source": "https://github.com/nextcloud/coding-standard/tree/v1.4.0" + }, + "time": "2025-06-19T12:27:27+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "php-cs-fixer/shim", + "version": "v3.65.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "4983ec79b9dee926695ac324ea6e8d291935525d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/4983ec79b9dee926695ac324ea6e8d291935525d", + "reference": "4983ec79b9dee926695ac324ea6e8d291935525d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "replace": { + "friendsofphp/php-cs-fixer": "self.version" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer", + "php-cs-fixer.phar" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "issues": "https://github.com/PHP-CS-Fixer/shim/issues", + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.65.0" + }, + "time": "2024-11-25T00:39:41+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/7bae67520aa9f5ecc506d646810bd40d9da54582", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^2.0", + "phpstan/phpdoc-parser": "^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26", + "shipmonk/dead-code-detector": "^0.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.3" + }, + "time": "2026-03-18T20:49:53+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0" + }, + "time": "2026-01-06T21:53:42+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + }, + "time": "2026-01-25T14:56:51+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "revolt/event-loop", + "version": "v1.0.8", + "source": { + "type": "git", + "url": "https://github.com/revoltphp/event-loop.git", + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/b6fc06dce8e9b523c9946138fa5e62181934f91c", + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.15" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Revolt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "ceesjank@gmail.com" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Rock-solid event loop for concurrent PHP applications.", + "keywords": [ + "async", + "asynchronous", + "concurrency", + "event", + "event-loop", + "non-blocking", + "scheduler" + ], + "support": { + "issues": "https://github.com/revoltphp/event-loop/issues", + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.8" + }, + "time": "2025-08-27T21:33:23+00:00" + }, + { + "name": "sebastian/diff", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:46+00:00" + }, + { + "name": "spatie/array-to-xml", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/88b2f3852a922dd73177a68938f8eb2ec70c7224", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "spatie/pest-plugin-snapshots": "^1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Spatie\\ArrayToXml\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" + } + ], + "description": "Convert an array to xml", + "homepage": "https://github.com/spatie/array-to-xml", + "keywords": [ + "array", + "convert", + "xml" + ], + "support": { + "source": "https://github.com/spatie/array-to-xml/tree/3.4.4" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-12-15T09:00:41+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.4.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-06T14:06:20+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3ebc794fa5315e59fd122561623c2e2e4280538e", + "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-25T16:50:00+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880", - "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "composer-plugin-api": "^2.0", - "php": "^7.2.5 || ^8.0" + "ext-iconv": "*", + "php": ">=7.2" }, - "require-dev": { - "composer/composer": "^2.0", - "ext-json": "*", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.5", - "symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", - "symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", - "symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0" + "provide": { + "ext-mbstring": "*" }, - "type": "composer-plugin", + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", "extra": { - "class": "Bamarni\\Composer\\Bin\\BamarniBinPlugin" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Bamarni\\Composer\\Bin\\": "src" + "Symfony\\Polyfill\\Mbstring\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "No conflicts for your bin dependencies", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", "keywords": [ - "composer", - "conflict", - "dependency", - "executable", - "isolation", - "tool" + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/bamarni/composer-bin-plugin/issues", - "source": "https://github.com/bamarni/composer-bin-plugin/tree/1.8.2" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, - "time": "2022-10-31T08:38:03+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" }, { - "name": "kubawerlos/php-cs-fixer-custom-fixers", - "version": "v3.22.0", + "name": "symfony/polyfill-php84", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers.git", - "reference": "8701394f0c7cd450ac4fa577d24589122c1d5d5e" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kubawerlos/php-cs-fixer-custom-fixers/zipball/8701394f0c7cd450ac4fa577d24589122c1d5d5e", - "reference": "8701394f0c7cd450ac4fa577d24589122c1d5d5e", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", "shasum": "" }, "require": { - "ext-filter": "*", - "ext-tokenizer": "*", - "friendsofphp/php-cs-fixer": "^3.61.1", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.6.4 || ^10.5.29" + "php": ">=7.2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "PhpCsFixerCustomFixers\\": "src" - } + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -99,41 +3136,175 @@ ], "authors": [ { - "name": "Kuba Werłos", - "email": "werlos@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "A set of custom fixers for PHP CS Fixer", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "issues": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/issues", - "source": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/tree/v3.22.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" }, - "time": "2024-08-16T20:44:35+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" }, { - "name": "nextcloud/coding-standard", - "version": "v1.3.2", + "name": "symfony/service-contracts", + "version": "v3.6.1", "source": { "type": "git", - "url": "https://github.com/nextcloud/coding-standard.git", - "reference": "9c719c4747fa26efc12f2e8b21c14a9a75c6ba6d" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud/coding-standard/zipball/9c719c4747fa26efc12f2e8b21c14a9a75c6ba6d", - "reference": "9c719c4747fa26efc12f2e8b21c14a9a75c6ba6d", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { - "kubawerlos/php-cs-fixer-custom-fixers": "^3.22", - "php": "^7.3|^8.0", - "php-cs-fixer/shim": "^3.17" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, "autoload": { "psr-4": { - "Nextcloud\\CodingStandard\\": "src" + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } + ], + "time": "2025-07-15T11:30:57+00:00" + }, + { + "name": "symfony/string", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -141,68 +3312,226 @@ ], "authors": [ { - "name": "Christoph Wurst", - "email": "christoph@winzerhof-wurst.at" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Nextcloud coding standards for the php cs fixer", + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], "support": { - "issues": "https://github.com/nextcloud/coding-standard/issues", - "source": "https://github.com/nextcloud/coding-standard/tree/v1.3.2" + "source": "https://github.com/symfony/string/tree/v7.4.6" }, - "time": "2024-10-14T16:49:05+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-09T09:33:46+00:00" }, { - "name": "php-cs-fixer/shim", - "version": "v3.65.0", + "name": "vimeo/psalm", + "version": "6.16.1", "source": { "type": "git", - "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "4983ec79b9dee926695ac324ea6e8d291935525d" + "url": "https://github.com/vimeo/psalm.git", + "reference": "f1f5de594dc76faf8784e02d3dc4716c91c6f6ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/4983ec79b9dee926695ac324ea6e8d291935525d", - "reference": "4983ec79b9dee926695ac324ea6e8d291935525d", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/f1f5de594dc76faf8784e02d3dc4716c91c6f6ac", + "reference": "f1f5de594dc76faf8784e02d3dc4716c91c6f6ac", "shasum": "" }, "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/parallel": "^2.3", + "composer-runtime-api": "^2", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^2.0 || ^3.0", + "danog/advanced-json-rpc": "^3.1", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-ctype": "*", + "ext-dom": "*", "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" + "felixfbecker/language-server-protocol": "^1.5.3", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", + "netresearch/jsonmapper": "^5.0", + "nikic/php-parser": "^5.0.0", + "php": "~8.1.31 || ~8.2.27 || ~8.3.16 || ~8.4.3 || ~8.5.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "spatie/array-to-xml": "^2.17.0 || ^3.0", + "symfony/console": "^6.0 || ^7.0 || ^8.0", + "symfony/filesystem": "~6.3.12 || ~6.4.3 || ^7.0.3 || ^8.0", + "symfony/polyfill-php84": "^1.31.0" }, - "replace": { - "friendsofphp/php-cs-fixer": "self.version" + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "amphp/phpunit-util": "^3", + "bamarni/composer-bin-plugin": "^1.4", + "brianium/paratest": "^6.9", + "danog/class-finder": "^0.4.8", + "dg/bypass-finals": "^1.5", + "ext-curl": "*", + "mockery/mockery": "^1.5", + "nunomaduro/mock-final-classes": "^1.1", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpdoc-parser": "^1.6", + "phpunit/phpunit": "^9.6", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.19", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.6", + "symfony/process": "^6.0 || ^7.0 || ^8.0" }, "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" }, "bin": [ - "php-cs-fixer", - "php-cs-fixer.phar" + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalm-review", + "psalter" ], - "type": "application", + "type": "project", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev", + "dev-3.x": "3.x-dev", + "dev-4.x": "4.x-dev", + "dev-5.x": "5.x-dev", + "dev-6.x": "6.x-dev", + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Matthew Brown" }, { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" + "name": "Daniil Gentili", + "email": "daniil@daniil.it" } ], - "description": "A tool to automatically fix PHP code style", + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php", + "static analysis" + ], "support": { - "issues": "https://github.com/PHP-CS-Fixer/shim/issues", - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.65.0" + "docs": "https://psalm.dev/docs", + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm" }, - "time": "2024-11-25T00:39:41+00:00" + "time": "2026-03-19T10:56:09+00:00" + }, + { + "name": "webmozart/assert", + "version": "2.1.6", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/ff31ad6efc62e66e518fbab1cde3453d389bcdc8", + "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-feature/2-0": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.1.6" + }, + "time": "2026-02-27T10:28:38+00:00" } ], "aliases": [], @@ -211,7 +3540,8 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "ext-curl": "*" + "ext-curl": "*", + "ext-simplexml": "*" }, "platform-dev": { "ext-fileinfo": "*"